├── Cartfile
├── Cartfile.resolved
├── Amigo.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── Amigo.xcscmblueprint
└── xcshareddata
│ └── xcschemes
│ └── Amigo.xcscheme
├── Amigo
├── MetaItem.swift
├── SchemaItem.swift
├── EngineFactory.swift
├── DefaultMapper.swift
├── Models
│ └── SQLParams.swift
├── AmigoEntityDescriptionMapper.swift
├── Update.swift
├── Delete.swift
├── typeToTableName.swift
├── Insert.swift
├── Filterable.swift
├── SQLiteFormat.h
├── Amigo.h
├── MetaData.swift
├── AmigoBatchOperation.swift
├── Mapper.swift
├── SQLiteEngineFactory.swift
├── AmigoModel.swift
├── OrderBy.swift
├── Compiler.swift
├── MetaModel.swift
├── Engine.swift
├── ForeignKey.swift
├── SQLiteEngine+Schema.swift
├── Info.plist
├── Expressions.swift
├── AmigoSessionAsyncAction.swift
├── ThreadLocalEngineFactory.swift
├── SQLiteTypeCompiler.swift
├── Index.swift
├── AmigoConfiguration.swift
├── TypeCompiler.swift
├── SQLiteFormat.m
├── Table.swift
├── SQLiteEngine+QuerySet.swift
├── SQLiteEngine+Master.swift
├── SQLiteEngine+Transactions.swift
├── NSManagedObjectModel+Sorted.swift
├── Relationship.swift
├── EntityDescriptionMapper.swift
├── ORMModel.swift
├── AmigoSessionModelAction.swift
├── AmigoMetaData.swift
├── Column.swift
├── SQLiteEngine.swift
├── Fields.swift
├── Select.swift
├── Amigo.swift
└── QuerySet.swift
├── docs
├── models
│ ├── index.rst
│ └── mom.rst
├── setup
│ ├── engine.rst
│ ├── index.rst
│ └── orm_models.rst
├── index.rst
├── sessions
│ └── index.rst
├── querying
│ └── index.rst
├── Makefile
└── make.bat
├── AmigoTests
├── Info.plist
├── AmigoTests.swift
├── AmigoTestBase.swift
├── EntityDescriptionMapperTests.swift
├── SelectTests.swift
├── Models.swift
├── SQLiteFormatTests.swift
├── AmigoMeta.swift
├── SQLiteEngineTests.swift
├── MOMExtensionTests.swift
├── AmigoSessionAsyncTests.swift
├── MetaTableTests.swift
├── PerfTests.swift
├── MetaColumnTests.swift
├── ModelMappingTests.swift
├── AmigoQuerySetTests.swift
└── App.xcdatamodeld
│ └── App.xcdatamodel
│ └── contents
├── .gitignore
├── LICENSE
└── README.md
/Cartfile:
--------------------------------------------------------------------------------
1 | github "ccgus/fmdb" ~> 2.6
2 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "ccgus/fmdb" "2.6"
2 |
--------------------------------------------------------------------------------
/Amigo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Amigo/MetaItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetaItem.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/25/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | public protocol MetaItem{
11 |
12 | }
--------------------------------------------------------------------------------
/Amigo/SchemaItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SchemaItem.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | public protocol SchemaItem: MetaItem{
11 |
12 | }
--------------------------------------------------------------------------------
/Amigo/EngineFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EngineFactory.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/22/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol EngineFactory{
12 | func connect() -> Engine
13 | }
--------------------------------------------------------------------------------
/Amigo/DefaultMapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultMapper.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/21/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class DefaultMapper: Mapper{
12 | public let metadata = MetaData()
13 |
14 | public required init(){}
15 | }
--------------------------------------------------------------------------------
/Amigo/Models/SQLParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLParams.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/13/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct SQLParams{
12 | let queryParams: [AnyObject]
13 | let defaultValues: [String: AnyObject]
14 | let automaticPrimaryKey: Bool
15 | }
--------------------------------------------------------------------------------
/Amigo/AmigoEntityDescriptionMapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoEntityDescriptionMapper.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/21/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public protocol AmigoEntityDescriptionMapper: Mapper{
13 | func map(entity: NSEntityDescription) -> ORMModel
14 | }
15 |
--------------------------------------------------------------------------------
/Amigo/Update.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Update.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/23/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Update: Filterable{
12 | public let table: Table
13 | public var predicate: String?
14 | public var predicateParams: [AnyObject]?
15 |
16 | public init(_ table: Table){
17 | self.table = table
18 | }
19 | }
--------------------------------------------------------------------------------
/Amigo/Delete.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Delete.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/21/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public class Delete: Filterable {
13 | public let table: Table
14 | public var predicate: String?
15 | public var predicateParams: [AnyObject]?
16 |
17 | public init(_ table: Table){
18 | self.table = table
19 | }
20 | }
--------------------------------------------------------------------------------
/docs/models/index.rst:
--------------------------------------------------------------------------------
1 | Models
2 | =================================
3 |
4 | Amigo works by mapping your data models derived from a
5 | :code:`Amigo.AmigoModel` into an :code:`Amigo.ORMModel`
6 |
7 | There are 2 way you can perform this mapping.
8 |
9 | 1. Use a Managed Object Model
10 | 2. Manual Mapping
11 |
12 | The Managed Object Model mapping just automates the Manual Mapping
13 | process.
14 |
15 | Contents:
16 |
17 | .. toctree::
18 | :maxdepth: 2
19 |
20 | mom
21 | orm_models
22 |
--------------------------------------------------------------------------------
/Amigo/typeToTableName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // typeToTableName.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 8/2/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | func typeToTableName(type: T.Type) -> String{
13 | return typeToTableName(type.description())
14 | }
15 |
16 | func typeToTableName(type: String) -> String{
17 | let parts = type.unicodeScalars.split{ $0 == "." }.map{ String($0).lowercaseString }
18 | return parts.joinWithSeparator("_")
19 | }
--------------------------------------------------------------------------------
/Amigo/Insert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Insert.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/19/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public class Insert {
13 | public let table: Table
14 | public let upsert: Bool
15 |
16 | public init(_ table: Table){
17 | self.table = table
18 | self.upsert = false
19 | }
20 |
21 | public init(_ table: Table, upsert: Bool = false){
22 | self.table = table
23 | self.upsert = upsert
24 | }
25 | }
--------------------------------------------------------------------------------
/Amigo/Filterable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Filterable.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/21/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol Filterable{
12 | var predicate: String? {get set}
13 | var predicateParams: [AnyObject]? {get set}
14 | }
15 |
16 |
17 | extension Filterable{
18 |
19 | mutating func filter(value: String, params:[AnyObject]? = nil) -> Self{
20 | predicate = value
21 | predicateParams = params
22 |
23 | return self
24 | }
25 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteFormat.h:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteFormat.h
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/14/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | #pragma once
10 |
11 | @interface SQLiteFormat : NSObject
12 | + (nonnull NSString *)format:(nullable char *)format, ...;
13 | + (nonnull NSString *)escapeWithQuotes:(nullable NSString *)value;
14 | + (nonnull NSString *)escapeWithoutQuotes:(nullable NSString *)value;
15 | + (nonnull NSString *)escapeBlob:(nullable NSData *)value;
16 | + (nonnull NSString *)hexStringWithData:(nonnull NSData *)data;
17 | @end
18 |
19 |
--------------------------------------------------------------------------------
/Amigo/Amigo.h:
--------------------------------------------------------------------------------
1 | //
2 | // Amigo.h
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | //! Project version number for Amigo.
13 | FOUNDATION_EXPORT double AmigoVersionNumber;
14 |
15 | //! Project version string for Amigo.
16 | FOUNDATION_EXPORT const unsigned char AmigoVersionString[];
17 |
18 | // In this header, you should import all the public headers of your framework using statements like #import
19 |
20 | #import
21 |
22 |
--------------------------------------------------------------------------------
/Amigo/MetaData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetaData.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/5/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class MetaData{
12 | public var tables = [String: Table]()
13 | var _sortedTables = [Table]()
14 |
15 | public var sortedTables:[Table]{
16 | return _sortedTables
17 | }
18 |
19 | public init(){
20 |
21 | }
22 |
23 | public func createAll(engine: Engine){
24 | let sql = engine.generateSchema(self)
25 | engine.createAll(sql)
26 | }
27 | }
--------------------------------------------------------------------------------
/Amigo/AmigoBatchOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BatchOperation.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/14/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol AmigoBatchOperation{
12 | init(session: AmigoSession)
13 |
14 | func add(obj: T)
15 | func add(obj: T, upsert: Bool)
16 | func add(obj: [T], upsert: Bool)
17 | func add(obj: [T])
18 |
19 | func delete(obj: T)
20 | func delete(obj: [T])
21 |
22 | func execute()
23 | }
--------------------------------------------------------------------------------
/Amigo/Mapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mapper.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/12/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public protocol Mapper{
13 | init()
14 | }
15 |
16 | extension Mapper{
17 | func instanceFromString(value: String) -> AmigoModel{
18 | let type = NSClassFromString(value) as! NSObject.Type
19 | let obj = type.init() as! AmigoModel
20 | return obj
21 | }
22 |
23 | func dottedNameToTableName(name: String) -> String{
24 | return typeToTableName(name)
25 | }
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Amigo/SQLiteEngineFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngineFactory.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/22/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct SQLiteEngineFactory: EngineFactory{
12 | public let path: String
13 | public let echo: Bool
14 | public let engine: SQLiteEngine
15 |
16 | public init(_ path: String, echo: Bool = false){
17 | self.path = path
18 | self.echo = echo
19 | self.engine = SQLiteEngine(path, echo: echo)
20 | }
21 |
22 | public func connect() -> Engine {
23 | return engine
24 | }
25 | }
--------------------------------------------------------------------------------
/Amigo/AmigoModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoModel.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/2/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 |
13 | @objc
14 | public class AmigoModel: NSObject{
15 | static var amigoModelIndex = [String: ORMModel]()
16 |
17 | public override init(){
18 | super.init()
19 | let _ = amigoModel
20 | }
21 |
22 | lazy var qualifiedName: String = {
23 | return self.dynamicType.description()
24 | }()
25 |
26 | lazy var amigoModel: ORMModel = {
27 | return AmigoModel.amigoModelIndex[self.qualifiedName]!
28 | }()
29 | }
--------------------------------------------------------------------------------
/Amigo/OrderBy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderBy.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/28/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol OrderBy{
12 | var keyPath: String {get}
13 | var ascending: Bool {get}
14 |
15 | init(_ keyPath: String)
16 | }
17 |
18 | public struct Asc: OrderBy{
19 | public let ascending = true
20 | public let keyPath: String
21 |
22 | public init(_ keyPath: String){
23 | self.keyPath = keyPath
24 | }
25 | }
26 |
27 | public struct Desc: OrderBy{
28 | public let ascending = false
29 | public let keyPath: String
30 |
31 | public init(_ keyPath: String){
32 | self.keyPath = keyPath
33 | }
34 | }
--------------------------------------------------------------------------------
/Amigo/Compiler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Compiler.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol Compiler{
12 | func compile(expression: CreateTable) -> String
13 | func compile(expression: CreateColumn) -> String
14 | func compile(expression: CreateIndex) -> String
15 | func compile(expression: Join) -> String
16 | func compile(expression: Select) -> String
17 | func compile(expression: Insert) -> String
18 | func compile(expression: Update) -> String
19 | func compile(expression: Delete) -> String
20 | func compile(expression: NSPredicate, table: Table, models: [String: ORMModel]) -> (String, [AnyObject])
21 | }
--------------------------------------------------------------------------------
/Amigo/MetaModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetaModel.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/3/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //public struct MetaModel{
12 | //
13 | // public let table: Table
14 | // public let primaryKey: Column
15 | // public let columns: [Column]
16 | // public let foreignKeys: [ForeignKey]?
17 | //
18 | // // public List ManyToMany { get; set; }
19 | // // public List ModelTypes { get; set; }
20 | //
21 | // init(table: Table, columns:[Column], primaryKey: Column, foreignKeys:[ForeignKey]? = nil){
22 | // self.table = table
23 | // self.columns = columns
24 | // self.primaryKey = primaryKey
25 | // self.foreignKeys = foreignKeys
26 | // }
27 | //
28 | //}
29 |
--------------------------------------------------------------------------------
/AmigoTests/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 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | docs/_build
3 | docs/_static
4 | docs/_templates
5 |
6 | # Xcode
7 | #
8 | build/
9 | *.pbxuser
10 | !default.pbxuser
11 | *.mode1v3
12 | !default.mode1v3
13 | *.mode2v3
14 | !default.mode2v3
15 | *.perspectivev3
16 | !default.perspectivev3
17 | xcuserdata
18 | *.xccheckout
19 | *.moved-aside
20 | DerivedData
21 | *.hmap
22 | *.ipa
23 | *.xcuserstate
24 |
25 | # CocoaPods
26 | #
27 | # We recommend against adding the Pods directory to your .gitignore. However
28 | # you should judge for yourself, the pros and cons are mentioned at:
29 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
30 | #
31 | # Pods/
32 |
33 | # Carthage
34 | #
35 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
36 |
37 | Carthage/Checkouts
38 | Carthage/Build
39 |
--------------------------------------------------------------------------------
/Amigo/Engine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Engine.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/2/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 |
10 | public protocol Engine {
11 |
12 | var compiler: Compiler {get}
13 | var fetchLastRowIdAfterInsert: Bool {get}
14 |
15 |
16 | func createAll(sql: String)
17 | func generateSchema(meta: MetaData) -> String
18 | func lastrowid() -> Int
19 |
20 | func execute(sql: String, params: [AnyObject]!, mapper: Input -> Output) -> Output
21 | func execute(sql: String, params: [AnyObject]!)
22 | func execute(sql: String)
23 | func execute(queryset: QuerySet) -> [T]
24 | func createBatchOperation(session: AmigoSession) -> AmigoBatchOperation
25 |
26 | func beginTransaction()
27 | func commitTransaction()
28 | func rollback()
29 | }
--------------------------------------------------------------------------------
/Amigo/ForeignKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForeignKey.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/3/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ForeignKey{
12 | public let relatedColumn: Column
13 | public var column: Column!
14 |
15 | public init(_ relatedColumn: Column, column: Column){
16 | self.relatedColumn = relatedColumn
17 | self.column = column
18 | }
19 |
20 | public init(_ relatedColumn: Column){
21 | self.relatedColumn = relatedColumn
22 | }
23 |
24 | public init(_ table: Table){
25 | self.relatedColumn = table.primaryKey!
26 | }
27 |
28 | public init(_ model: ORMModel){
29 | self.relatedColumn = model.table.primaryKey!
30 | }
31 |
32 | public var relatedTable: Table{
33 | return relatedColumn.table!
34 | }
35 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteEngine+Schema.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine+Schema.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/6/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 |
13 | extension SQLiteEngine{
14 |
15 | public func generateSchema(meta: MetaData) -> String{
16 | let tables = meta.sortedTables.map{
17 | compiler.compile(CreateTable($0))
18 | }
19 |
20 | return tables.joinWithSeparator("\n")
21 | }
22 |
23 | public func createAll(sql: String){
24 |
25 | db.inTransaction { (db, rollback) -> Void in
26 |
27 | if self.echo{
28 | self.echo(sql)
29 | }
30 |
31 | // needs to be executeStatements,
32 | // AKA Multiple SQL Commands
33 | db.executeStatements(sql)
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/Amigo/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 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.3.3
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Amigo/Expressions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CreateTable.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/6/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Expression{
12 | let element: T
13 |
14 | public init(element: T){
15 | self.element = element
16 | }
17 | }
18 |
19 |
20 | public class CreateColumn: Expression{
21 | public init(_ column: Column){
22 | super.init(element: column)
23 | }
24 | }
25 |
26 |
27 | public class CreateIndex: Expression{
28 | public init(_ index: Index){
29 | super.init(element: index)
30 | }
31 | }
32 |
33 |
34 | public class CreateTable: Expression{
35 | let columns: [CreateColumn]
36 |
37 | public init(_ table: Table){
38 | self.columns = table.sortedColumns.map(CreateColumn.init)
39 | super.init(element: table)
40 | }
41 | }
--------------------------------------------------------------------------------
/Amigo/AmigoSessionAsyncAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoSessionAsyncAction.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/16/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public class AmigoSessionAsyncAction{
13 |
14 | var action: (() -> T)!
15 | var queue: dispatch_queue_t!
16 |
17 | public init(action: () -> T, queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
18 | self.action = action
19 | self.queue = queue
20 | }
21 |
22 |
23 | public func run(){
24 | dispatch_async(queue){
25 | self.action()
26 | }
27 | }
28 |
29 | public func then(complete: (T) -> ()){
30 | dispatch_async(queue){
31 | let results = self.action()
32 |
33 | dispatch_async(dispatch_get_main_queue()){
34 | complete(results)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/AmigoTests/AmigoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoTests.swift
3 | // AmigoTests
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import FMDB
12 | import Amigo
13 |
14 | class AmigoTests: AmigoTestBase {
15 |
16 | func testSelectRelated(){
17 | let select: Select
18 | let sql: String
19 | let query = amigo.query(People)
20 | query.selectRelated("dog", "cat")
21 |
22 | select = query.compile()
23 | sql = amigo.config.engine.compiler.compile(select)
24 | print(sql)
25 | }
26 |
27 | func testSelectValues(){
28 | let select: Select
29 | let sql: String
30 | let query = amigo.query(People)
31 | query.selectRelated("dog")
32 | query.values("id", "label", "dog.id")
33 |
34 | select = query.compile()
35 | sql = amigo.config.engine.compiler.compile(select)
36 | print(sql)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/models/mom.rst:
--------------------------------------------------------------------------------
1 | Managed Object Model
2 | ===================================
3 |
4 |
5 | Yup, Amigo can turn :code:`NSEntityDescriptions` along with their
6 | relationships into your tables for you. There are only a couple things to know.
7 |
8 |
9 | 1. Unlike :code:`CoreData`, you need to specify your primary key field.
10 | This could totally be automated for you, we havent decided if we like
11 | that or not yet. You do this by picking your attribute in your entity
12 | and adding the following to the User Info: primaryKey true.
13 | Crack open the :code:`App.xcdatamodeld` and look at any of the entities
14 | for more info.
15 |
16 | 2. You need to be sure the `Class` you assign to the entity in your
17 | `xcdatamodeld` is a subclass of AmigoModel
18 |
19 |
20 | You do not have to use a :code:`NSManagedObjectModel` at all. In fact,
21 | we just use it to convert the :code:`NSEntityDescriptions` into
22 | :code: `ORMModel` instances. It's fine if you want to do the mappings
23 | yourself. See :doc:`orm_models` for more.
24 |
--------------------------------------------------------------------------------
/AmigoTests/AmigoTestBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoTestBase.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/23/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 |
14 | class AmigoTestBase: XCTestCase {
15 |
16 | var amigo: Amigo!
17 | var engine: SQLiteEngineFactory!
18 |
19 | override func setUp() {
20 | super.setUp()
21 | let name = "App"
22 | let bundle = NSBundle(forClass: self.dynamicType)
23 | let url = NSURL(string:bundle.pathForResource(name, ofType: "momd")!)!
24 | let mom = NSManagedObjectModel(contentsOfURL: url)!
25 |
26 | engine = SQLiteEngineFactory(":memory:", echo: true)
27 | amigo = Amigo(mom, factory: engine)
28 | amigo.createAll()
29 | }
30 |
31 | override func tearDown() {
32 | // Put teardown code here. This method is called after the invocation of each test method in the class.
33 | super.tearDown()
34 | (amigo.config.engine as! SQLiteEngine).db.close()
35 | }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/docs/setup/engine.rst:
--------------------------------------------------------------------------------
1 | Engine Setup
2 | =================================
3 |
4 | An instance of Amigo needs an engine to run the queries. Amigo comes
5 | with a `SQLiteEngine` out of the box. Let's take a look at setting it
6 | up so we can create our schema:
7 |
8 | .. code-block:: swift
9 |
10 | import Amigo
11 | import CoreData
12 |
13 | // the first arg can be ":memory:" for an in-memory
14 | // database, or it can be the absolute path to your
15 | // sqlite database.
16 | //
17 | // echo : Boolean
18 | // true prints out the SQL statements with params
19 | // the default value of false does nothing.
20 |
21 | let mom = NSManagedObjectModel(contentsOfURL: url)!
22 |
23 | // specifying 'echo: true' will have amigo print out
24 | // all of the SQL commands it's generating.
25 | let engine = SQLiteEngineFactory(":memory:", echo: true)
26 | amigo = Amigo(mom, factory: engine)
27 | amigo.createAll()
28 |
29 | Note, in the example above `Amigo` can process a :code:`ManagedObjectModel`
30 | to create it's schema. See :doc:`models/mom` for more.
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 BLITZ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Amigo/ThreadLocalEngineFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThreadLocalEngineFactory.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/22/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol ThreadLocalEngineFactory: EngineFactory{
12 | func createEngine() -> Engine
13 | }
14 |
15 | extension ThreadLocalEngineFactory{
16 |
17 | func engineForCurrentThread() -> Engine? {
18 | let dict = NSThread.currentThread().threadDictionary
19 | let obj = dict["amigo.threadlocal.engine"] as? Engine
20 |
21 | if let obj = obj{
22 | return obj
23 | } else {
24 | return nil
25 | }
26 | }
27 |
28 | func createEngineForCurrentThread() -> Engine {
29 | let engine = createEngine()
30 | let dict = NSThread.currentThread().threadDictionary
31 | dict["amigo.threadlocal.engine"] = engine as? AnyObject
32 |
33 | return engine
34 | }
35 |
36 | public func connect() -> Engine {
37 | if let engine = engineForCurrentThread(){
38 | return engine
39 | } else {
40 | return createEngineForCurrentThread()
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to Amigo!
2 | =================================
3 |
4 | A SQLite ORM for Swift 2.1+ powered by `FMDB `_
5 |
6 |
7 | But What About CoreData?
8 | ------------------------
9 |
10 | Yup, CoreData...
11 |
12 |
13 | Requirements
14 | ------------------------
15 |
16 | - Swift 2.1 (aka iOS 9+, os x 10.11+)
17 | - CoreData
18 |
19 |
20 | The Deal
21 | ------------------------
22 |
23 | So, we like CoreData, but we also like ORMs in the flavor of SQLAlchemy
24 | or Django's ORM. So lets make that happen, however unwisely, in Swift.
25 | To that end you could say at this point that Amigo was firstly inspired
26 | by the version of Amigo we wrote for C#/Xamarin which you can find here:
27 |
28 | https://github.com/blitzagency/amigo
29 |
30 | But what if we just want to work in Swift you say? Well, lets rewrite
31 | Amigo in Swift then! So we did. It's a start. A start that shamelessly
32 | copies, probably poorly, ideas found in SQLAlchemy and Django.
33 |
34 |
35 |
36 | Contents:
37 |
38 | .. toctree::
39 | :maxdepth: 2
40 |
41 | setup/index
42 | models/index
43 | querying/index
44 | sessions/index
45 |
46 |
47 | Indices and tables
48 | ==================
49 |
50 | * :ref:`genindex`
51 | * :ref:`modindex`
52 | * :ref:`search`
53 |
54 |
--------------------------------------------------------------------------------
/Amigo/SQLiteTypeCompiler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteTypeCompiler.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | public struct SQLiteTypeCompiler : TypeCompiler{
14 |
15 | public func compileInteger16() -> String{
16 | return "INTEGER"
17 | }
18 |
19 | public func compileInteger32() -> String{
20 | return "INTEGER"
21 | }
22 |
23 | public func compileInteger64() -> String{
24 | return "INTEGER"
25 | }
26 |
27 | public func compileString() -> String{
28 | return "TEXT"
29 | }
30 |
31 | public func compileBoolean() -> String{
32 | return "INTEGER"
33 | }
34 |
35 | public func compileDate() -> String{
36 | return "TEXT"
37 | }
38 |
39 | public func compileBinaryData() -> String{
40 | return "BLOB"
41 | }
42 |
43 | public func compileDecimal() -> String{
44 | return "REAL"
45 | }
46 |
47 | public func compileDouble() -> String{
48 | return "REAL"
49 | }
50 |
51 | public func compileFloat() -> String{
52 | return "REAL"
53 | }
54 |
55 | public func compileUndefined() -> String{
56 | return "BLOB"
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/AmigoTests/EntityDescriptionMapperTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EntityDescriptionMapperTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/9/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 |
14 | class EntityDescriptionMapperTests: XCTestCase {
15 | var mapper: EntityDescriptionMapper!
16 | var mom: NSManagedObjectModel!
17 |
18 | override func setUp() {
19 | super.setUp()
20 |
21 | let name = "App"
22 | let bundle = NSBundle(forClass: self.dynamicType)
23 | let url = bundle.URLForResource(name, withExtension: "momd")!
24 |
25 | mom = NSManagedObjectModel(contentsOfURL: url)!
26 | mapper = EntityDescriptionMapper()
27 | }
28 |
29 | override func tearDown() {
30 | // Put teardown code here. This method is called after the invocation of each test method in the class.
31 | super.tearDown()
32 | }
33 |
34 | func testMapper() {
35 | let model = mapper.map(mom.entitiesByName["Dog"]!)
36 | XCTAssertEqual(model.table.label, "amigotests_dog")
37 | XCTAssertEqual(model.type, "AmigoTests.Dog")
38 |
39 | }
40 |
41 | func testOneToManyMapping(){
42 | let entities = [
43 | mom.entitiesByName["Author"]!,
44 | mom.entitiesByName["Post"]!
45 | ]
46 |
47 | let models = entities.map(mapper.map)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Amigo/Index.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Index.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Index: SchemaItem, CustomStringConvertible{
12 | public let label: String
13 | public let unique: Bool
14 |
15 | public var columns: [Column]!
16 | public var table: Table? {
17 | didSet{
18 | if let strings = columnStrings{
19 | columns = strings.map{ table!.c[$0]! }
20 | }
21 | }
22 | }
23 |
24 | var columnStrings: [String]?
25 |
26 | public convenience init(_ label: String, unique: Bool = false, _ columns: String...){
27 | self.init(label, unique: unique, columns: columns)
28 | }
29 |
30 | public convenience init(_ label: String, unique: Bool = false, columns: [String] ){
31 | self.init(label, unique: unique, columns: nil)
32 | columnStrings = columns
33 | }
34 |
35 | public convenience init(_ label: String, unique: Bool = false, columns: Column...){
36 | self.init(label, unique: unique, columns: columns)
37 | }
38 |
39 | public init(_ label: String, unique: Bool = false, columns maybeColumns: [Column]? ){
40 | self.label = label
41 | self.unique = unique
42 |
43 | if let columns = maybeColumns{
44 | self.columns = columns
45 | self.table = columns[0].table
46 | }
47 | }
48 |
49 | public var description: String{
50 | return ""
51 | }
52 | }
--------------------------------------------------------------------------------
/Amigo.xcodeproj/project.xcworkspace/xcshareddata/Amigo.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "76CDF08CE51248DC9FD59BC936CA918CEA0DC715",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "04101981B66E318109934C9516F88D824BB96459" : 0,
8 | "76CDF08CE51248DC9FD59BC936CA918CEA0DC715" : 0
9 | },
10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "2EAF0BE7-B796-4B0B-BD31-F6F210E71F42",
11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 | "04101981B66E318109934C9516F88D824BB96459" : "..\/..\/..\/..\/github\/fmdb",
13 | "76CDF08CE51248DC9FD59BC936CA918CEA0DC715" : "Amigo\/"
14 | },
15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Amigo",
16 | "DVTSourceControlWorkspaceBlueprintVersion" : 203,
17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Amigo.xcodeproj",
18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 | {
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:aventurella\/fmdb.git",
21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "04101981B66E318109934C9516F88D824BB96459"
23 | },
24 | {
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:blitzagency\/amigo-swift.git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "76CDF08CE51248DC9FD59BC936CA918CEA0DC715"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/AmigoTests/SelectTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Amigo
11 |
12 | class SelectTests: XCTestCase {
13 |
14 | var meta: MetaData!
15 |
16 | override func setUp() {
17 | super.setUp()
18 | meta = MetaData()
19 |
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | super.tearDown()
25 | }
26 |
27 | func testTableSelect() {
28 | let t1 = Table("dogs", metadata: meta,
29 | Column("id", type: Int.self, primaryKey: true),
30 | Column("label", type: String.self)
31 | )
32 |
33 | let select = Select(t1)
34 | XCTAssertEqual(select.columns.count, 2)
35 | }
36 |
37 | func testTableSelectJoin(){
38 | let t1 = Table("dogs", metadata: meta,
39 | Column("id", type: Int.self, primaryKey: true),
40 | Column("label", type: String.self)
41 | )
42 |
43 | let t2 = Table("cats", metadata: meta,
44 | Column("id", type: Int.self, primaryKey: true),
45 | Column("label", type: String.self)
46 | )
47 |
48 | let t3 = Table("people", metadata: meta,
49 | Column("id", type: Int.self, primaryKey: true),
50 | Column("label", type: String.self),
51 | Column("dog_id", type: ForeignKey(t1)),
52 | Column("cat_id", type: ForeignKey(t2))
53 | )
54 |
55 | let j1 = t3.join(t1)
56 | let j2 = t3.join(t2)
57 | let select = Select(t3).selectFrom(j1, j2)
58 |
59 | }
60 |
61 |
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/Amigo/AmigoConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoConfiguration.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public protocol AmigoConfigured{
13 | var config: AmigoConfiguration {get}
14 | }
15 |
16 | extension AmigoConfigured{
17 | var mapper: Mapper{
18 | return config.mapper
19 | }
20 |
21 | var engine: Engine{
22 | return config.engine
23 | }
24 |
25 | var tableIndex: [String: ORMModel]{
26 | return config.tableIndex
27 | }
28 |
29 | var typeIndex: [String: ORMModel]{
30 | return config.typeIndex
31 | }
32 |
33 | }
34 |
35 | public struct AmigoConfiguration{
36 |
37 | public let engine: Engine
38 | public let mapper: Mapper
39 | public let tableIndex: [String: ORMModel]
40 | public let typeIndex: [String: ORMModel]
41 |
42 |
43 | static var documentsDirectory: String{
44 | let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
45 | return urls[urls.count-1].path!
46 | }
47 |
48 | // public static var defaultConfiguration: AmigoConfiguration{
49 | // return AmigoConfiguration.fromName("App")
50 | // }
51 |
52 | // public static func fromName(name: String) -> AmigoConfiguration{
53 | // let dir = AmigoConfiguration.documentsDirectory.stringByAppendingPathComponent("\(name).sqlite")
54 | // let engine = SQLiteEngine(path: dir)
55 | // let mapper = EntityDescriptionMapper()
56 | //
57 | // return AmigoConfiguration(engine: engine, mapper: mapper)
58 | // }
59 | //
60 | // public static func fromMemory() -> AmigoConfiguration{
61 | // let engine = SQLiteEngine(path: ":memory:")
62 | // let mapper = EntityDescriptionMapper()
63 | //
64 | // return AmigoConfiguration(engine: engine, mapper: mapper)
65 | // }
66 | }
--------------------------------------------------------------------------------
/Amigo/TypeCompiler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TypeCompiler.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public protocol TypeCompiler{
13 | func process(type: NSAttributeType) -> String
14 | func compileInteger16() -> String
15 | func compileInteger32() -> String
16 | func compileInteger64() -> String
17 | func compileString() -> String
18 | func compileBoolean() -> String
19 | func compileDate() -> String
20 | func compileBinaryData() -> String
21 | func compileDecimal() -> String
22 | func compileDouble() -> String
23 | func compileFloat() -> String
24 | func compileUndefined() -> String
25 | }
26 |
27 |
28 | extension TypeCompiler{
29 | public func process(type: NSAttributeType) -> String {
30 | switch type{
31 | case .Integer16AttributeType:
32 | return compileInteger16()
33 | case .Integer32AttributeType:
34 | return compileInteger32()
35 | case .Integer64AttributeType:
36 | return compileInteger64()
37 | case .StringAttributeType:
38 | return compileString()
39 | case .BooleanAttributeType:
40 | return compileBoolean()
41 | case .DateAttributeType:
42 | return compileDate()
43 | case .BinaryDataAttributeType:
44 | return compileBinaryData()
45 | case .DecimalAttributeType:
46 | return compileDecimal()
47 | case .DoubleAttributeType:
48 | return compileDouble()
49 | case .FloatAttributeType:
50 | return compileFloat()
51 | case .UndefinedAttributeType:
52 | return compileUndefined()
53 | default:
54 | // NSAttributeType.TransformableAttributeType
55 | // NSAttributeType.ObjectIDAttributeType
56 | return compileUndefined()
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/AmigoTests/Models.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Models.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 | import Amigo
12 |
13 | // ---- To One ----
14 | // A `People` can only have 1 dog and 1 Cat
15 |
16 | class Dog: AmigoModel{
17 | dynamic var id: NSNumber!
18 | dynamic var label: String!
19 | }
20 |
21 | class Cat: AmigoModel{
22 | dynamic var id: NSNumber!
23 | dynamic var label: String!
24 | }
25 |
26 | class People: AmigoModel{
27 | dynamic var id: NSNumber!
28 | dynamic var label: String!
29 | dynamic var dog: Dog!
30 | dynamic var cat: Cat!
31 | }
32 |
33 |
34 | // ---- One To Many ----
35 | // We only allow 1 `Author` per `Post`
36 | // But an author can have Many Posts
37 |
38 | class Author: AmigoModel {
39 | dynamic var id: NSNumber!
40 | dynamic var firstName: String!
41 | dynamic var lastName: String!
42 | }
43 |
44 | class Post: AmigoModel{
45 | dynamic var id: NSNumber!
46 | dynamic var title: String!
47 | dynamic var author: Author!
48 | }
49 |
50 |
51 | // ---- Many To Many ----
52 | // A Parent can have Many Children
53 | // and children can have Many Parents
54 |
55 | class Parent: AmigoModel{
56 | dynamic var id: NSNumber!
57 | dynamic var label: String!
58 | }
59 |
60 | class Child: AmigoModel{
61 | dynamic var id: NSNumber!
62 | dynamic var label: String!
63 | }
64 |
65 | // ---- Many To Many (through model) ----
66 |
67 | class Workout: AmigoModel{
68 | dynamic var id: NSNumber!
69 | dynamic var label: String!
70 | }
71 |
72 | class WorkoutExercise: AmigoModel{
73 | dynamic var id: NSNumber!
74 | dynamic var label: String!
75 | }
76 |
77 | class WorkoutMeta: AmigoModel{
78 | dynamic var id: NSNumber!
79 | dynamic var duration: NSNumber!
80 | dynamic var position: NSNumber!
81 | dynamic var exercise: WorkoutExercise!
82 | dynamic var workout: Workout!
83 | }
--------------------------------------------------------------------------------
/AmigoTests/SQLiteFormatTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteFormatTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/14/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Amigo
11 |
12 | class SQLiteFormatTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testEscapeNilWithQuotes() {
23 | let value: String? = nil
24 | let result = SQLiteFormat.escapeWithQuotes(value)
25 | XCTAssert(result == "NULL")
26 | }
27 |
28 | func testEscapeNilWithoutQuotes() {
29 | let value: String? = nil
30 | let result = SQLiteFormat.escapeWithoutQuotes(value)
31 | XCTAssert(result == "(NULL)")
32 | }
33 |
34 | func testEscapeWithQuotes() {
35 | let value = "test's"
36 | let result = SQLiteFormat.escapeWithQuotes(value)
37 | XCTAssert(result == "'test''s'")
38 | }
39 |
40 | func testEscapeWithoutQuotes() {
41 | let value = "test's"
42 | let result = SQLiteFormat.escapeWithoutQuotes(value)
43 | XCTAssert(result == "test''s")
44 | }
45 |
46 | func testEscapeBlob() {
47 |
48 | let uuid = NSUUID()
49 | var bytes = [UInt8](count: 16, repeatedValue: 0)
50 | uuid.getUUIDBytes(&bytes)
51 |
52 | let data = NSData(bytes: bytes, length: bytes.count)
53 |
54 | let hex = SQLiteFormat.hexStringWithData(data)
55 | let result = SQLiteFormat.escapeBlob(data)
56 |
57 | XCTAssert(result == "x'\(hex)'")
58 | }
59 |
60 | func testEscapeEmptyBlob() {
61 |
62 | let data = NSData()
63 | let result = SQLiteFormat.escapeBlob(data)
64 |
65 | XCTAssert(result == "NULL")
66 | }
67 |
68 | func testEscapeNilBlob() {
69 |
70 | var data: NSData?
71 | let result = SQLiteFormat.escapeBlob(data)
72 |
73 | XCTAssert(result == "NULL")
74 | }
75 | }
--------------------------------------------------------------------------------
/AmigoTests/AmigoMeta.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoMeta.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/1/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 |
12 | @testable import Amigo
13 |
14 | class AmigoMeta: XCTestCase {
15 | var amigo: Amigo!
16 | var config: AmigoConfiguration!
17 |
18 | override func setUp() {
19 | super.setUp()
20 |
21 | let bundle = NSBundle(forClass: self.dynamicType)
22 | config = AmigoConfiguration.fromName("App", bundle: bundle)
23 | amigo = Amigo(config: config)
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testCreateAll(){
32 | //let meta = AmigoMetaData(amigo.managedObjectModel)
33 | //let engine = SQLiteEngine(path: NSURL(string:"")!)
34 | // engine.createAll(meta)
35 | }
36 |
37 |
38 | func testMetaModelForType() {
39 | //let meta = AmigoMetaData(amigo.managedObjectModel)
40 | //let value = meta.metaModelForType(Author)
41 | //XCTAssertNotNil(value)
42 | //let engine = SQLiteEngine(path: NSURL(string:"")!)
43 | //meta.createAll(engine)
44 | // This is an example of a functional test case.
45 | // Use XCTAssert and related functions to verify your tests produce the correct results.
46 | }
47 |
48 | func testSQLLiteEngine(){
49 | // let session = amigo.session
50 | // let engine = session.config.engine as! SQLiteEngine
51 | // let query = session.query(Post)
52 | //
53 | // engine.createQueryFields(<#T##query: T##T#>, model: <#T##MetaModel#>)
54 | // .all()
55 | }
56 |
57 |
58 |
59 | func testPerformanceExample() {
60 |
61 | // This is an example of a performance test case.
62 | self.measureBlock() {
63 | // Put the code you want to measure the time of here.
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/AmigoTests/SQLiteEngineTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/3/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import FMDB
11 | @testable import Amigo
12 |
13 | class SQLiteEngineTests: XCTestCase {
14 |
15 | var engine: SQLiteEngine!
16 | var meta: MetaData!
17 |
18 | override func setUp() {
19 | super.setUp()
20 |
21 | meta = MetaData()
22 | engine = SQLiteEngine(":memory:")
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | super.tearDown()
28 | engine.db.close()
29 | }
30 |
31 | // func testCreateSchema(){
32 | // let done = expectationWithDescription("Done")
33 | //
34 | //
35 | // let _ = Table("dogs", metadata: meta,
36 | // Column("id", type: Int.self, primaryKey: true),
37 | // Column("name", type: String.self)
38 | // )
39 | //
40 | // meta.createAll(engine)
41 | //
42 | // engine.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"){ (result: FMResultSet) in
43 | //
44 | // //XCTAssert(result.count == 1)
45 | // //XCTAssert(result[0][0] as! String == "dogs")
46 | // done.fulfill()
47 | // }
48 | //
49 | // waitForExpectationsWithTimeout(5.0, handler:nil)
50 | //
51 | // }
52 |
53 | func testSelect(){
54 | // let done = expectationWithDescription("Done")
55 | //
56 | // let _ = Table("dogs", metadata: meta,
57 | // Column("id", type: Int.self, primaryKey: true),
58 | // Column("name", type: String.self)
59 | // )
60 | //
61 | // meta.createAll(engine)
62 | //
63 | // let master = Table("sqlite_master", metadata: meta,
64 | // Column("name", type: String.self)
65 | // )
66 | //
67 | // engine.query(master.select()){ result in
68 | // XCTAssert(result.count == 1)
69 | // XCTAssert(result[0][0] as! String == "dogs")
70 | // done.fulfill()
71 | // }
72 | //
73 | // waitForExpectationsWithTimeout(5.0, handler:nil)
74 |
75 | }
76 |
77 |
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/Amigo/SQLiteFormat.m:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteFormat.m
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/14/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "sqlite3.h"
11 | #import "SQLiteFormat.h"
12 |
13 | @implementation SQLiteFormat
14 | + (nonnull NSString *)format:(nullable char *)format, ...{
15 | va_list ap;
16 | va_start(ap, format);
17 |
18 | char *result = sqlite3_vmprintf(format, ap);
19 | NSString * string = @(result);
20 | sqlite3_free(result);
21 |
22 | va_end(ap);
23 | return string;
24 | }
25 |
26 | + (nonnull NSString *)escapeWithQuotes:(nullable NSString *)value{
27 | const char* utf8 = value.UTF8String;
28 | return [SQLiteFormat format:"%Q", utf8];
29 | }
30 |
31 | + (nonnull NSString *)escapeWithoutQuotes:(nullable NSString *)value{
32 | const char* utf8 = value.UTF8String;
33 | return [SQLiteFormat format:"%q", utf8];
34 | }
35 |
36 | + (nonnull NSString *)escapeBlob:(nullable NSData *)value {
37 | if (value == nil){
38 | return [self escapeWithQuotes: nil];
39 | }
40 |
41 | NSString* hex = [self hexStringWithData:value];
42 |
43 | if (hex == [NSString string]){
44 | return [self escapeWithQuotes: nil];
45 | }
46 |
47 | NSString* escapedHex = [self escapeWithoutQuotes: hex];
48 |
49 | // http://permalink.gmane.org/gmane.comp.db.sqlite.general/64150
50 | // https://www.sqlite.org/lang_expr.html
51 | // BLOB literals are string literals containing hexadecimal data and
52 | // preceded by a single "x" or "X" character. Example: X'53514C697465'
53 | NSString* result = [NSString stringWithFormat:@"x'%@'", escapedHex];
54 |
55 | return result;
56 | }
57 |
58 | + (nonnull NSString *)hexStringWithData:(nonnull NSData *)data{
59 | // https://gist.github.com/hlung/6333269
60 |
61 | const unsigned char* dataBuffer = (const unsigned char *)[data bytes];
62 | NSUInteger dataLength = [data length];
63 |
64 | if (!dataBuffer) {
65 | return [NSString string];
66 | }
67 |
68 | NSMutableString* hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
69 |
70 | for (int i = 0; i < dataLength; ++i) {
71 | [hexString appendString:[NSString stringWithFormat: @"%02lx", (unsigned long)dataBuffer[i]]];
72 | }
73 |
74 | return [NSString stringWithString: [hexString lowercaseString]];
75 | }
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/Amigo/Table.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Table.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/3/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public func ==(lhs: Table, rhs: Table) -> Bool {
13 | return lhs.hashValue == rhs.hashValue
14 | }
15 |
16 |
17 | public class Table: SchemaItem, FromClause{
18 | public let label: String
19 | public let metadata: MetaData
20 | public var primaryKey: Column?
21 | public var columns = [String: Column]()
22 | public var indexes = [Index]()
23 | var model: ORMModel?
24 |
25 | var _sortedColumns = [Column]() // represents the order added
26 |
27 | public var hashValue: Int {
28 | return description.hashValue
29 | }
30 |
31 | public var sortedColumns: [Column]{
32 | return _sortedColumns
33 | }
34 |
35 |
36 | public var c: [String: Column]{
37 | return columns
38 | }
39 |
40 | public convenience init(_ label: String, metadata: MetaData, _ items: SchemaItem...){
41 | self.init(label, metadata: metadata, items: items)
42 | }
43 |
44 | public init(_ label: String, metadata: MetaData, items: [SchemaItem]){
45 |
46 | self.label = label
47 | self.metadata = metadata
48 |
49 | let addIndex = { (value: Index) -> () in
50 | value.table = self
51 | self.indexes.append(value)
52 | }
53 |
54 | let addColumn = { (value: Column) -> () in
55 |
56 | value._table = self
57 | self.columns[value.label.lowercaseString] = value
58 | self._sortedColumns.append(value)
59 |
60 | if value.primaryKey{
61 | self.primaryKey = value
62 | }
63 |
64 | if value.indexed{
65 | let label = "\(self.label)_\(value.label)_idx"
66 | if value.unique{
67 | addIndex(Index(label, unique: true, columns:value))
68 | } else {
69 | addIndex(Index(label, columns:value))
70 | }
71 | }
72 | }
73 |
74 | items.filter{$0 is Column}.forEach{addColumn($0 as! Column)}
75 | items.filter{$0 is Index}.forEach{addIndex($0 as! Index)}
76 |
77 | metadata.tables[label] = self
78 | metadata._sortedTables.append(self)
79 | }
80 |
81 | public var description: String {
82 | return ""
83 | }
84 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteEngine+QuerySet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine+QuerySet.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/24/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 | extension SQLiteEngine{
13 | public func execute(queryset:QuerySet) -> [T]{
14 | let select = queryset.compile()
15 | let sql = compiler.compile(select)
16 | let model = queryset.model
17 | let models = queryset.config.tableIndex
18 | let mapper = queryset.mapper
19 | var result = [T]()
20 |
21 | var objectMap = [ORMModel: String]()
22 | var params: [AnyObject]!
23 |
24 | if let predicateParams = select.predicateParams{
25 | params = predicateParams
26 | }
27 |
28 | if select.from.count > 1{
29 | model.foreignKeys.forEach{ (key, value)-> () in
30 | let ormModel = models[value.foreignKey!.relatedTable.label]!
31 | objectMap[ormModel] = key
32 | }
33 | }
34 |
35 | let modelMapper = { (results: FMResultSet) -> [T] in
36 |
37 | var rows = [T]()
38 |
39 | while results.next(){
40 | var objs = [ORMModel: AmigoModel]()
41 | var currentModel = model
42 |
43 | objs[currentModel] = mapper.instanceFromString(model.type)
44 |
45 | select.columns.forEach{
46 | var active: AmigoModel!
47 | currentModel = models[$0.table!.label]!
48 |
49 | if let obj = objs[currentModel]{
50 | active = obj
51 | } else {
52 | let root = objs[model]!
53 | let key = objectMap[currentModel]!
54 | active = mapper.instanceFromString(currentModel.type)
55 | objs[currentModel] = active
56 | root.setValue(active, forKey: key)
57 | }
58 |
59 | let value = results.objectForColumnName($0.qualifiedLabel!)
60 | active.setValue($0.deserialize(value), forKey: $0.label)
61 | }
62 |
63 | rows.append(objs[model]! as! T)
64 | }
65 |
66 | results.close()
67 | return rows
68 | }
69 |
70 | result = execute(sql, params: params){ (results: FMResultSet) -> [T] in
71 | return modelMapper(results)
72 | }
73 |
74 | return result
75 | }
76 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteEngine+Master.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine+Master.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/22/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 | public struct SQLiteMasterRow : CustomStringConvertible{
13 | let type: String
14 | let name : String
15 | let tableName: String
16 | let rootpage: Int
17 | let sql: String
18 |
19 | public var description: String{
20 | return "\(self.dynamicType)(\(tableName))"
21 | }
22 | }
23 |
24 | extension SQLiteEngine{
25 | public func master() -> [SQLiteMasterRow]{
26 |
27 | return execute("SELECT * FROM sqlite_master ORDER BY name"){ (result:FMResultSet) -> [SQLiteMasterRow] in
28 | var rows = [SQLiteMasterRow]()
29 |
30 | while result.next(){
31 | let row = SQLiteMasterRow(
32 | type: result.stringForColumn("type"),
33 | name: result.stringForColumn("name"),
34 | tableName: result.stringForColumn("tbl_name"),
35 | rootpage: Int(result.intForColumn("rootpage")),
36 | sql: result.stringForColumn("sql")
37 | )
38 |
39 | rows.append(row)
40 | }
41 | return rows
42 | }
43 | }
44 |
45 | public func masterTables() -> [SQLiteMasterRow]{
46 |
47 | return execute("SELECT * FROM sqlite_master WHERE type='table' ORDER BY name"){ (result:FMResultSet) -> [SQLiteMasterRow] in
48 | var rows = [SQLiteMasterRow]()
49 |
50 | while result.next(){
51 | let row = SQLiteMasterRow(
52 | type: result.stringForColumn("type"),
53 | name: result.stringForColumn("name"),
54 | tableName: result.stringForColumn("tbl_name"),
55 | rootpage: Int(result.intForColumn("rootpage")),
56 | sql: result.stringForColumn("sql")
57 | )
58 |
59 | rows.append(row)
60 | }
61 |
62 | return rows
63 | }
64 | }
65 |
66 | public func masterTable(label: String) -> SQLiteMasterRow?{
67 |
68 | return execute("SELECT * FROM sqlite_master WHERE type='table' AND name=? ORDER BY name", params: [label]){ (result:FMResultSet) -> SQLiteMasterRow? in
69 | var rows = [SQLiteMasterRow]()
70 |
71 | while result.next(){
72 | let row = SQLiteMasterRow(
73 | type: result.stringForColumn("type"),
74 | name: result.stringForColumn("name"),
75 | tableName: result.stringForColumn("tbl_name"),
76 | rootpage: Int(result.intForColumn("rootpage")),
77 | sql: result.stringForColumn("sql")
78 | )
79 |
80 | rows.append(row)
81 | }
82 |
83 | return rows.first
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteEngine+Transactions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine+Transactions.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/19/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 | import CoreData
12 |
13 | extension SQLiteEngine{
14 |
15 |
16 | // public func add(obj: T, model: ORMModel){
17 | // let insert = model.table.insert()
18 | // let sql = compiler.compile(insert)
19 | // var automaticPrimaryKey = false
20 | //
21 | // var values = [AnyObject]()
22 | //
23 | // for each in model.table.sortedColumns{
24 | // if each.primaryKey && each.type == .Integer64AttributeType{
25 | // automaticPrimaryKey = true
26 | // continue
27 | // }
28 | //
29 | // let value: AnyObject?
30 | //
31 | //
32 | // if let column = each.foreignKey{
33 | // print("++++ WHEN SAVING FOREIGN KEYS +++ -> SQLiteEngine+Transactions -> add()")
34 | // continue
35 | // } else {
36 | // value = obj.valueForKey(each.label)
37 | // }
38 | //
39 | // values.append(value!)
40 | // }
41 | //
42 | // db.inDatabase { (db) -> Void in
43 | // db.executeUpdate(sql, withArgumentsInArray: values)
44 | //
45 | // if automaticPrimaryKey {
46 | // let result = db.executeQuery("SELECT last_insert_rowid();", withArgumentsInArray: nil)
47 | // result.next()
48 | // obj.setValue(result.objectForColumnIndex(0), forKey: model.primaryKey.label)
49 | // result.close()
50 | // }
51 | // }
52 | // }
53 | //
54 | // public func delete(obj: T, model: ORMModel){
55 | // let id = model.primaryKey.label
56 | // let value = obj.valueForKey(id)
57 | // let delete = model.table.delete().filter("\(id) = \(value)")
58 | // //compiler.compile(<#T##expression: NSPredicate##NSPredicate#>, model: <#T##ORMModel#>, models: <#T##[String : ORMModel]#>)
59 | // //let sql = compiler.compile(delete)
60 | //// var automaticPrimaryKey = false
61 | ////
62 | //// var values = [AnyObject]()
63 | ////
64 | //// for each in model.table.sortedColumns{
65 | //// if each.primaryKey && each.type == .Integer64AttributeType{
66 | //// automaticPrimaryKey = true
67 | //// continue
68 | //// }
69 | ////
70 | //// let value: AnyObject?
71 | ////
72 | ////
73 | //// if let column = each.foreignKey{
74 | //// continue
75 | //// } else {
76 | //// value = obj.valueForKey(each.label)
77 | //// }
78 | ////
79 | //// values.append(value!)
80 | //// }
81 | ////
82 | //// db.inDatabase { (db) -> Void in
83 | //// db.executeUpdate(sql, withArgumentsInArray: values)
84 | //// }
85 | // }
86 |
87 | }
--------------------------------------------------------------------------------
/docs/sessions/index.rst:
--------------------------------------------------------------------------------
1 | Sessions
2 | =================================
3 |
4 |
5 | When you go though :code:`amigo.session` using the provided
6 | :code:`SQLiteEngine` you automatically begin a SQL Transaction.
7 |
8 | If you would like your information to actually be persisted you must
9 | :code:`commit` the transaction. Once committed, the session will
10 | automatically begin a new transaciton for you.
11 |
12 |
13 | .. code-block:: swift
14 |
15 | import Amigo
16 |
17 | class Dog: AmigoModel{
18 | dynamic var id: NSNumber!
19 | dynamic var label: String!
20 | }
21 |
22 | class Person: AmigoModel{
23 | dynamic var id: NSNumber!
24 | dynamic var label: String!
25 | dynamic var dog: Dog!
26 | }
27 |
28 | let dog = ORMModel(Dog.self,
29 | Column("id", type: Int.self, primaryKey: true),
30 | Column("label", type: String.self),
31 | OneToMany("people", using: Person.self, on: "dog")
32 | )
33 |
34 | let person = ORMModel(Person.self,
35 | Column("id", type: Int.self, primaryKey: true),
36 | Column("label", type: String.self),
37 | Column("dog", type: ForeignKey(dog))
38 | )
39 |
40 | // specifying 'echo: true' will have amigo print out
41 | // all of the SQL commands it's generating.
42 | let engine = SQLiteEngineFactory(":memory:", echo: true)
43 | amigo = Amigo([dog, person], factory: engine)
44 | amigo.createAll()
45 |
46 | let session = amigo.session
47 | let d1 = Dog()
48 | let d2 = Dog()
49 |
50 | d1.label = "Lucy"
51 | d2.label = "Ollie"
52 |
53 | session.add(d1, d2)
54 | session.commit()
55 |
56 |
57 | Upsert
58 | ------------------------
59 |
60 | When inserting a model, you have the option to choose weather or not
61 | you would like this to be done as an insert or an upsert. In sqlite
62 | this translates to :code:`INSERT OR REPLACE`. To take advantage of this
63 | you need to pass an additional argument to :code:`session.add`.
64 |
65 | .. code-block:: swift
66 |
67 | session.add(myModel, upsert: true)
68 |
69 |
70 | Batching
71 | ------------------------
72 |
73 | If you would like to batch a significant number of queries
74 | Amigo supports this for add and delete.
75 |
76 | .. code-block:: swift
77 |
78 | let session = amigo.session
79 |
80 | session.batch{ bacth in
81 | myAdds.forEach(batch.add)
82 | myDeletes.forEach(batch.add)
83 | myUpserts.forEach{ batch.add($0, upsert: true) }
84 | }
85 |
86 | This will take all of the generated sql and execute at once. It's a
87 | convenience over FMDB :code:`executeStatements`
88 | https://github.com/ccgus/fmdb#multiple-statements-and-batch-stuff
89 |
90 | .. important::
91 |
92 | When you use this functionality Amigo does not make any
93 | updates to the source models. For example, doing an
94 | :code:`session.add` will modify the source model with
95 | the primary key assigned to it by immediately issuing a
96 | :code:`SELECT last_insert_rowid();`. However, :code:`batch.add`
97 | will not do this.
98 |
99 | .. warning::
100 |
101 | If you use batching with Many-To-Many + Through Models you should
102 | have all of the information necessary in advance for the
103 | relationship. It's not required, but if you don't have all of the
104 | Foreign Keys and Primary Key for the record, Amigo will skip batching
105 | those items in favor of a regular :code:`session.add` to ensure
106 | it has the proper information.
107 |
--------------------------------------------------------------------------------
/Amigo/NSManagedObjectModel+Sorted.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSManagedObject+Sorted.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/14/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | //extension NSEntityDescription{
13 | // public override var description: String{
14 | // return self.name!
15 | // }
16 | //}
17 |
18 | extension NSManagedObjectModel{
19 |
20 | public func sortedEntities() -> [NSEntityDescription] {
21 | let list = buildDependencyList()
22 | return topologicalSort(list)
23 | }
24 |
25 | func validateTopologicalSort(graph: [NSEntityDescription:[NSEntityDescription]]) -> Bool {
26 | for (key, value) in graph {
27 | if value.count > 0 {
28 | fatalError("Dependency Cycle for Entity: \(key)")
29 | }
30 | }
31 |
32 | return true
33 | }
34 |
35 | public func topologicalSort(dependencyList:[NSEntityDescription:[NSEntityDescription]]) -> [NSEntityDescription]{
36 | // https://gist.github.com/mrecachinas/0704e8110983fff94ae9
37 |
38 | var sorted = [NSEntityDescription]()
39 | var next_depth = [NSEntityDescription]()
40 | var graph = dependencyList
41 |
42 | for key in graph.keys {
43 | if graph[key]! == [] {
44 | next_depth.append(key)
45 | }
46 | }
47 |
48 | for key in next_depth {
49 | graph.removeValueForKey(key)
50 | }
51 |
52 | while next_depth.count != 0 {
53 |
54 | next_depth = next_depth.sort{ $0.name! > $1.name }
55 |
56 | let node = next_depth.removeLast()
57 | sorted.append(node)
58 |
59 | for key in graph.keys {
60 | let arr = graph[key]
61 | let dl = arr!.filter{ $0 == node}
62 | if dl.count > 0 {
63 | graph[key] = graph[key]?.filter({$0 != node})
64 | if graph[key]?.count == 0 {
65 | next_depth.append(key)
66 | }
67 | }
68 | }
69 | }
70 |
71 | validateTopologicalSort(graph)
72 | return sorted
73 | }
74 |
75 | public func buildDependencyList() -> [NSEntityDescription:[NSEntityDescription]]{
76 | var dependencies = [NSEntityDescription:[NSEntityDescription]]()
77 |
78 | // initialize the list
79 | for each in entities{
80 | dependencies[each] = []
81 | }
82 |
83 | for each in entities{
84 | for (_, relationship) in each.relationshipsByName {
85 |
86 | if relationship.toMany == false{
87 | dependencies[each]!.append(relationship.destinationEntity!)
88 | }
89 | }
90 | }
91 |
92 | return dependencies
93 | }
94 |
95 | public func buildDependencyListStrings() -> [String:[String]]{
96 | var dependencies = [String:[String]]()
97 |
98 | // initialize the list
99 | for each in entities{
100 | dependencies[each.name!] = []
101 | }
102 |
103 | for each in entities{
104 | for (_, relationship) in each.relationshipsByName {
105 |
106 | if relationship.toMany == false{
107 | dependencies[each.name!]!.append(relationship.destinationEntity!.name!)
108 | }
109 | }
110 | }
111 |
112 | return dependencies
113 | }
114 | }
--------------------------------------------------------------------------------
/AmigoTests/MOMExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MOMExtensionTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/14/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 | class MOMExtensionTests: XCTestCase {
14 |
15 | var mom: NSManagedObjectModel!
16 |
17 | override func setUp() {
18 | super.setUp()
19 | let name = "App"
20 | let bundle = NSBundle(forClass: self.dynamicType)
21 | let url = NSURL(string:bundle.pathForResource(name, ofType: "momd")!)!
22 | mom = NSManagedObjectModel(contentsOfURL: url)!
23 |
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testDependencyList() {
32 | let list = mom.buildDependencyList()
33 |
34 | // list.forEach{ key, item in
35 | // print("\(key.name): \(item.count)")
36 | // }
37 |
38 |
39 | let cat = mom.entitiesByName["Cat"]!
40 | let dog = mom.entitiesByName["Dog"]!
41 | let people = mom.entitiesByName["People"]!
42 |
43 | let post = mom.entitiesByName["Post"]!
44 | let author = mom.entitiesByName["Author"]!
45 |
46 | let parent = mom.entitiesByName["Parent"]!
47 | let child = mom.entitiesByName["Child"]!
48 |
49 | let workout = mom.entitiesByName["Workout"]!
50 | let workoutExercise = mom.entitiesByName["WorkoutExercise"]!
51 | let workoutMeta = mom.entitiesByName["WorkoutMeta"]!
52 |
53 | //[Post: [Author], Dog: [], Author: [], People: [Cat, Dog], Cat: [], Workout: [], WorkoutExercise: [], WorkoutMeta: [Workout, WorkoutExercise], Parent: [], Child: []]
54 |
55 |
56 | // Foreign Keys
57 | XCTAssert(list[author]!.count == 0)
58 | XCTAssert(list[post]!.count == 1)
59 |
60 | // Foreign Keys
61 | XCTAssert(list[people]!.count == 2)
62 | XCTAssert(list[dog]!.count == 0)
63 | XCTAssert(list[cat]!.count == 0)
64 |
65 | // Many to Many + Through Model
66 | XCTAssert(list[workout]!.count == 0)
67 | XCTAssert(list[workoutExercise]!.count == 0)
68 | XCTAssert(list[workoutMeta]!.count == 2)
69 |
70 | // Many To Many
71 | XCTAssert(list[parent]!.count == 0)
72 | XCTAssert(list[child]!.count == 0)
73 | }
74 |
75 | func testTopologicalSort() {
76 |
77 | let list = mom.buildDependencyList()
78 | let sorted = mom.topologicalSort(list)
79 |
80 | let author = mom.entitiesByName["Author"]!
81 | let post = mom.entitiesByName["Post"]!
82 |
83 | let cat = mom.entitiesByName["Cat"]!
84 | let dog = mom.entitiesByName["Dog"]!
85 | let people = mom.entitiesByName["People"]!
86 |
87 | let parent = mom.entitiesByName["Parent"]!
88 | let child = mom.entitiesByName["Child"]!
89 |
90 | let workout = mom.entitiesByName["Workout"]!
91 | let workoutExercise = mom.entitiesByName["WorkoutExercise"]!
92 | let workoutMeta = mom.entitiesByName["WorkoutMeta"]!
93 |
94 | // sorted.forEach{
95 | // print($0.name)
96 | // }
97 |
98 | // [Author, Cat, Child, Dog, Parent, People, Post, Workout, WorkoutExercise, WorkoutMeta]
99 | let expected = [author, cat, child, dog, parent, people, post, workout, workoutExercise, workoutMeta]
100 | XCTAssert(sorted == expected)
101 |
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/AmigoTests/AmigoSessionAsyncTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoSessionAsyncTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/16/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Amigo
11 |
12 |
13 | class AmigoSessionAsyncTests: AmigoTestBase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | override func tearDown() {
20 | super.tearDown()
21 | }
22 |
23 | func testAsyncActionNoResult() {
24 | let done = expectationWithDescription("done")
25 | let session = amigo.session
26 |
27 | let complete = {
28 | let results = session.query(Dog).all()
29 | XCTAssert(results.count == 1)
30 |
31 | done.fulfill()
32 | }
33 |
34 | session.async{
35 | let d1 = Dog()
36 | d1.label = "lucy"
37 | session.add(d1)
38 | complete()
39 | }
40 |
41 | waitForExpectationsWithTimeout(1.0, handler: nil)
42 |
43 | }
44 |
45 | func testAsyncActionResult() {
46 | let done = expectationWithDescription("done")
47 | let session = amigo.session
48 |
49 | session.async{ () -> Dog in
50 | let d1 = Dog()
51 | d1.label = "lucy"
52 | session.add(d1)
53 |
54 | return d1
55 |
56 | }.then{ dog in
57 |
58 | let results = session.query(Dog).all()
59 | XCTAssert(results.count == 1)
60 | XCTAssert(results[0].id == dog.id)
61 | XCTAssert(results[0].label == dog.label)
62 |
63 | done.fulfill()
64 | }
65 |
66 | waitForExpectationsWithTimeout(1.0, handler: nil)
67 |
68 | }
69 |
70 | func testAsyncActionNoResultThen() {
71 | let done = expectationWithDescription("done")
72 | let session = amigo.session
73 |
74 | session.async{
75 | let d1 = Dog()
76 | d1.label = "lucy"
77 | session.add(d1)
78 |
79 | }.then{
80 | let results = session.query(Dog).all()
81 | XCTAssert(results.count == 1)
82 |
83 | done.fulfill()
84 | }
85 |
86 | waitForExpectationsWithTimeout(1.0, handler: nil)
87 |
88 | }
89 |
90 | func testAsyncActionCustomQueue() {
91 | let queue = dispatch_queue_create("com.amigo.async.tests", nil)
92 |
93 | let done = expectationWithDescription("done")
94 | let session = amigo.session
95 |
96 | session.async(queue: queue){
97 | let d1 = Dog()
98 | d1.label = "lucy"
99 | session.add(d1)
100 |
101 | }.then{
102 | let results = session.query(Dog).all()
103 | XCTAssert(results.count == 1)
104 |
105 | done.fulfill()
106 | }
107 |
108 | waitForExpectationsWithTimeout(60.0, handler: nil)
109 |
110 | }
111 |
112 | func testAsyncResultsArray() {
113 |
114 | let done = expectationWithDescription("done")
115 | let session = amigo.session
116 |
117 | let callback : ([Dog]) -> () = { results in
118 | XCTAssert(results.count == 1)
119 | done.fulfill()
120 | }
121 |
122 | session.async{ () -> [Dog] in
123 | let d1 = Dog()
124 | d1.label = "lucy"
125 | session.add(d1)
126 | return session.query(Dog).all()
127 | }.then(callback)
128 |
129 |
130 | waitForExpectationsWithTimeout(60.0, handler: nil)
131 |
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/AmigoTests/MetaTableTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASTTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/5/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 | class MetaTableTests: XCTestCase {
14 |
15 | var metadata: MetaData!
16 |
17 | override func setUp() {
18 | metadata = MetaData()
19 | super.setUp()
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 | func testTableVariadicColumns() {
29 |
30 | let table = Table("dogs", metadata: metadata,
31 | Column("id", type: Int.self, primaryKey: true),
32 | Column("lucy", type: String.self)
33 | )
34 |
35 | XCTAssertTrue(table.c.count == 2)
36 | }
37 |
38 | func testTableVariadicMixed() {
39 |
40 | let table = Table("dogs", metadata: metadata,
41 | Column("id", type: Int.self, primaryKey: true),
42 | Column("lucy", type: String.self),
43 | Index("dogs_lucy_idx", "lucy")
44 | )
45 |
46 | XCTAssertTrue(table.c.count == 2)
47 | XCTAssertTrue(table.indexes.count == 1)
48 | }
49 |
50 | func testTableList() {
51 |
52 | let items:[SchemaItem] = [
53 | Column("id", type: Int.self, primaryKey: true),
54 | Column("lucy", type: String.self)]
55 |
56 | let table = Table("dogs", metadata: metadata, items: items)
57 |
58 | XCTAssertTrue(table.c.count == 2)
59 | }
60 |
61 | func testMetadata() {
62 |
63 | let items: [SchemaItem] = [
64 | Column("id", type: Int.self, primaryKey: true),
65 | Column("lucy", type: String.self)]
66 |
67 | let _ = Table("dogs", metadata: metadata, items: items)
68 |
69 | XCTAssertTrue(metadata.tables.values.count == 1)
70 | }
71 |
72 | func testColumnAliasAccess() {
73 |
74 | let items:[SchemaItem] = [
75 | Column("id", type: Int.self, primaryKey: true),
76 | Column("lucy", type: String.self)]
77 |
78 | let table = Table("dogs", metadata: metadata, items: items)
79 |
80 | XCTAssertNotNil(table.c["id"]!)
81 | }
82 |
83 | func testColumnTableAssociation() {
84 |
85 | let items: [SchemaItem] = [
86 | Column("id", type: Int.self, primaryKey: true),
87 | Column("lucy", type: String.self)]
88 |
89 | let table = Table("dogs", metadata: metadata, items: items)
90 | let id = table.c["id"]!
91 |
92 | if let _ = id.table {
93 | XCTAssertTrue(true)
94 | }
95 | else {
96 | XCTFail("Value isn't set")
97 | }
98 | }
99 |
100 | func testPrimaryKeyTrue() {
101 |
102 | let items: [SchemaItem] = [
103 | Column("id", type: Int.self, primaryKey: true),
104 | Column("lucy", type: String.self)]
105 |
106 | let table = Table("dogs", metadata: metadata, items: items)
107 |
108 | XCTAssertNotNil(table.primaryKey)
109 | }
110 |
111 | func testPrimaryKeyFalse() {
112 |
113 | let items: [SchemaItem] = [
114 | Column("id", type: Int.self),
115 | Column("lucy", type: String.self)]
116 |
117 | let table = Table("dogs", metadata: metadata, items: items)
118 |
119 | XCTAssertNil(table.primaryKey)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Amigo/Relationship.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Relationship.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/24/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // there is probably a way to combine ForeignKey With relationship, they
12 | // both are a relationship afterall. Need to think about that abstraction.
13 |
14 | public protocol Relationship: MetaItem{
15 | var label: String {get}
16 | var type: RelationshipType {get}
17 |
18 | }
19 |
20 | public enum RelationshipType{
21 | case OneToMany, ManyToMany
22 | }
23 |
24 | public func ==(lhs: ManyToMany, rhs: ManyToMany) -> Bool {
25 | return lhs.hashValue == rhs.hashValue
26 | }
27 |
28 | public class ManyToMany: Relationship, CustomStringConvertible, Hashable{
29 | public let label: String
30 | public let type = RelationshipType.ManyToMany
31 |
32 | var left: ORMModel!
33 | var right: ORMModel!
34 | var through: ORMModel?
35 | var tables: [String]!
36 | var throughModel: String?
37 | var associationTable: Table!
38 | var partial: ((type: String) -> Void)?
39 |
40 | public init(_ label: String, using: T.Type, throughModel: U.Type? = nil){
41 | self.label = label
42 | self.partial = self.partialInit(using, throughModel: throughModel)
43 | }
44 |
45 | public init(_ label: String, tables:[String], throughModel: String? = nil){
46 | self.label = label
47 | self.tables = tables.sort()
48 |
49 | if let throughModel = throughModel {
50 | self.throughModel = throughModel
51 | }
52 | }
53 |
54 | func partialInit
55 | (right: T.Type, throughModel: U.Type? = nil)
56 | (left: String){
57 |
58 | let r = typeToTableName(right)
59 | let l = typeToTableName(left)
60 |
61 | self.tables = [l, r].sort()
62 |
63 | if let throughModel = throughModel{
64 | self.throughModel = throughModel.description()
65 | }
66 |
67 | self.partial = nil
68 | }
69 |
70 | public lazy var tableName: String = {
71 | return self.tables.joinWithSeparator("_")
72 | }()
73 |
74 | public lazy var hashValue: Int = {
75 | return self.tables.joinWithSeparator("_").hashValue
76 | }()
77 |
78 | public var description: String{
79 | return ""
80 | }
81 | }
82 |
83 | public class OneToMany: Relationship, CustomStringConvertible{
84 | public let label: String
85 | public let type = RelationshipType.OneToMany
86 |
87 | let table: String
88 | var column: String!
89 | var originTable: String!
90 |
91 | public convenience init(_ label: String, using: T.Type){
92 | let tableName = typeToTableName(using)
93 | self.init(label, table: tableName, column: nil)
94 | }
95 |
96 | public init(_ label: String, table: String, column: String? = nil){
97 | self.label = label
98 | self.table = table
99 | self.column = column
100 | }
101 |
102 | func initOriginType(value: String){
103 | originTable = typeToTableName(value)
104 | }
105 |
106 | public var description: String{
107 | return ""
108 | }
109 | }
110 |
111 |
112 | //public class Relationship: MetaItem, CustomStringConvertible{
113 | // public let label: String
114 | // public let type: RelationshipType
115 | //
116 | // let relatedTableLabel: String
117 | // let relatedColumnLabel: String
118 | //
119 | // public init(_ label: String, table: String, column: String, type: RelationshipType){
120 | // self.label = label
121 | // self.type = type
122 | // self.relatedTableLabel = table
123 | // self.relatedColumnLabel = column
124 | // }
125 | //
126 | // public var description: String{
127 | // return ""
128 | // }
129 | //}
--------------------------------------------------------------------------------
/AmigoTests/PerfTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PerfTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/28/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | @testable import Amigo
12 |
13 | class PerfTests: XCTestCase {
14 |
15 | var amigo: Amigo!
16 | var engine: SQLiteEngineFactory!
17 |
18 | override func setUp() {
19 | super.setUp()
20 | let name = "App"
21 | let bundle = NSBundle(forClass: self.dynamicType)
22 | let url = NSURL(string:bundle.pathForResource(name, ofType: "momd")!)!
23 | let mom = NSManagedObjectModel(contentsOfURL: url)!
24 |
25 | engine = SQLiteEngineFactory(":memory:", echo: true)
26 | amigo = Amigo(mom, factory: engine)
27 | amigo.createAll()
28 | }
29 |
30 | override func tearDown() {
31 | // Put teardown code here. This method is called after the invocation of each test method in the class.
32 | super.tearDown()
33 | (amigo.config.engine as! SQLiteEngine).db.close()
34 | }
35 |
36 | func testExample() {
37 | // This is an example of a functional test case.
38 | // Use XCTAssert and related functions to verify your tests produce the correct results.
39 | }
40 |
41 | func testPerformanceBatch() {
42 | let session = amigo.session
43 |
44 | measureBlock{
45 | for _ in 0..<10000{
46 | let d = Dog()
47 | d.label = "foo"
48 |
49 | session.add(d)
50 | }
51 |
52 | session.commit()
53 | }
54 | }
55 |
56 | func testPerformancePerAdd() {
57 |
58 | measureBlock{
59 | for _ in 0..<10000{
60 | let session = self.amigo.session
61 | let d = Dog()
62 | d.label = "foo"
63 |
64 | session.add(d)
65 | session.commit()
66 | }
67 | }
68 | }
69 |
70 | func testBatchCreateItems() {
71 | var statements = [String]()
72 | let session = amigo.session
73 |
74 | let objs = (0..<10000).map{ _ -> Dog in
75 | let d = Dog()
76 | d.label = "lucy"
77 | return d
78 | }
79 |
80 | //AmigoSqlite3.test()
81 |
82 | self.measureBlock{
83 | objs.forEach{
84 | // let model = $0.amigoModel
85 | session.add($0)
86 | // let sql = session.insertSQL(model)
87 | // print(sql)
88 | // let params = session.insertParams($0)
89 | // //let sql = "something"
90 | // //statements.append(sql)
91 | }
92 | }
93 |
94 | }
95 |
96 | func testSQLiteBatchUpsert(){
97 | let session = amigo.session
98 |
99 | let objs = (0..<10).map{ _ -> Dog in
100 | let d = Dog()
101 | d.label = "lucy's"
102 | return d
103 | }
104 |
105 | self.measureBlock{
106 | session.batch{ batch in
107 | objs.forEach{
108 | batch.add($0, upsert: false)
109 | }
110 | }
111 | }
112 | }
113 |
114 | func testBatchJoinItem() {
115 | var statements = [String]()
116 |
117 | for _ in 0..<20000{
118 | let d = Dog()
119 | d.label = "foo"
120 | statements.append("INSERT INTO amigotests_dog (label) VALUES ('foo');")
121 | }
122 |
123 | self.measureBlock{
124 | statements.joinWithSeparator("\n")
125 | }
126 |
127 | }
128 |
129 | func testBatchMerge() {
130 | let a1 = [1, 2]
131 | let a2 = [3, 4]
132 | var out = [Int]()
133 |
134 | out.appendContentsOf(a1)
135 | out.appendContentsOf(a2)
136 |
137 | print(out)
138 |
139 |
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Amigo
2 | A SQLite ORM for Swift 2.1+ powered by FMDB
3 |
4 | Docs in progress are here:
5 |
6 | http://amigo.readthedocs.org/en/latest/
7 |
8 |
9 |
10 | ## Carthage
11 |
12 | FMDB (https://github.com/ccgus/fmdb),
13 | as of v 2.6 FMDB, supports Carthage.
14 |
15 | Drop this into your `Cartfile`:
16 |
17 | ```
18 | github "blitzagency/amigo-swift" ~> 0.3.0
19 | ```
20 |
21 |
22 | ## Fire it up
23 |
24 | Lets create a schema:
25 |
26 |
27 | ```swift
28 |
29 | import Amigo
30 | import CoreData
31 |
32 | // the first arg can be ":memory:" for an in-memory
33 | // database, or it can be the absolute path to your
34 | // sqlite database.
35 | //
36 | // echo : Boolean
37 | // true prints out the SQL statements with params
38 | // the default value of false does nothing.
39 |
40 | let mom = NSManagedObjectModel(contentsOfURL: url)!
41 |
42 | // specifying 'echo: true' will have amigo print out
43 | // all of the SQL commands it's generating.
44 | let engine = SQLiteEngineFactory(":memory:", echo: true)
45 | amigo = Amigo(mom, factory: engine)
46 | amigo.createAll()
47 | ```
48 |
49 | Yup, Amigo can turn NSEntityDescriptions along with their relationships
50 | into your tables for you.
51 |
52 | Read more about it here:
53 |
54 | http://amigo.readthedocs.org/en/latest/models/mom.html
55 |
56 | There are only a couple things to know.
57 |
58 | 1. Unlike CoreData, you need to specify your primary key field. This could
59 | totally be automated for you, we havent decided if we like that or not yet.
60 | You do this by picking your attribute in your entity and adding the following
61 | to the User Info: `primaryKey` `true`. Crack open the `App.xcdatamodeld`
62 | and look at any of the entities for more info.
63 |
64 | 2. You need to be sure the Class you assign to the entity in your `xcdatamodeld`
65 | is a subclass of `AmigoModel`
66 |
67 | You do not have to use a `ManagedObjectModel` either you can just define your
68 | mappings yourself as follows:
69 |
70 | Read more about it here:
71 |
72 | http://amigo.readthedocs.org/en/latest/models/orm_models.html
73 |
74 | ```swift
75 | import Amigo
76 |
77 | class Dog: AmigoModel{
78 | dynamic var id: Int = 0
79 | dynamic var label: String!
80 | }
81 |
82 | let dog = ORMModel(Dog.self,
83 | Column("id", type: Int.self, primaryKey: true)
84 | Column("label", type: String.self)
85 | Index("dog_label_idx", "label")
86 | )
87 |
88 | // you could achieve the same mapping this way:
89 |
90 | let dog = ORMModel(Dog.self,
91 | Column("id", type: Int.self, primaryKey: true)
92 | Column("label", type: String.self, indexed: true)
93 | )
94 |
95 | // now initialize Amigo
96 | // specifying 'echo: true' will have amigo print out
97 | // all of the SQL commands it's generating.
98 | let engine = SQLiteEngineFactory(":memory:", echo: true)
99 | amigo = Amigo([dog], factory: engine)
100 | amigo.createAll()
101 | ```
102 |
103 | Amigo Supports `ForeignKeys`, `OneToMany` and `ManyToMany` relationships.
104 | More on that later.
105 |
106 | ## Querying
107 |
108 | Using our mapping above, lets do some simple querying:
109 |
110 | You can also read more about this here:
111 |
112 | http://amigo.readthedocs.org/en/latest/querying/index.html
113 |
114 |
115 | ```swift
116 | import Amigo
117 |
118 | class Dog: AmigoModel{
119 | dynamic var id: NSNumber!
120 | dynamic var label: String!
121 | }
122 |
123 | let dog = ORMModel(Dog.self,
124 | Column("id", type: Int.self, primaryKey: true)
125 | Column("label", type: String.self)
126 | )
127 |
128 | // specifying 'echo: true' will have amigo print out
129 | // all of the SQL commands it's generating.
130 | let engine = SQLiteEngineFactory(":memory:", echo: true)
131 | amigo = Amigo([dog], factory: engine)
132 | amigo.createAll()
133 |
134 | // first lets add a dog
135 |
136 | let session = amigo.session
137 | let d1 = Dog()
138 | d1.label = "Lucy"
139 |
140 | session.add(d1)
141 |
142 | // now lets query:
143 | let results = session.query(Dog).all()
144 | print(results.count)
145 | ```
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/Amigo/EntityDescriptionMapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EntityDescriptionMapper.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/9/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 | // need to make an intermediate model to query against.
12 |
13 | public class EntityDescriptionMapper: AmigoEntityDescriptionMapper{
14 |
15 | public required init(){}
16 |
17 | public func map(entity: NSEntityDescription) -> ORMModel {
18 | let type = entity.managedObjectClassName
19 | var items: [MetaItem]
20 |
21 | items = Array(entity.propertiesByName.values).map(mapColumn)
22 | .filter{ $0 != nil }
23 | .map{ $0! }
24 |
25 | let model = ORMModel(type, properties: items)
26 | return model
27 | }
28 |
29 | func mapColumn(obj: NSPropertyDescription) -> MetaItem?{
30 | var column: MetaItem?
31 |
32 | if obj is NSAttributeDescription{
33 | column = mapAttributeColumn(obj as! NSAttributeDescription)
34 | } else if obj is NSRelationshipDescription{
35 | column = mapRelationshipColumn(obj as! NSRelationshipDescription)
36 | }
37 |
38 | return column
39 | }
40 |
41 | func mapAttributeColumn(obj: NSAttributeDescription) -> MetaItem{
42 | let name = obj.name
43 | let indexed = obj.indexed
44 | let optional = obj.optional
45 | let type = obj.attributeType
46 | let primaryKey: Bool
47 |
48 |
49 | if let pk = obj.userInfo?["primaryKey"] as? NSString{
50 | primaryKey = pk.boolValue
51 | } else{
52 | primaryKey = false
53 | }
54 |
55 | return Column(name,
56 | type: type,
57 | primaryKey: primaryKey,
58 | indexed: indexed,
59 | optional: optional)
60 | }
61 |
62 | func mapRelationshipColumn(obj: NSRelationshipDescription) -> MetaItem?{
63 |
64 | if obj.toMany == false{
65 | return mapToOneRelationship(obj)
66 | } else {
67 | return mapToManyRelationship(obj)
68 | }
69 | }
70 |
71 | func mapToManyRelationship(obj: NSRelationshipDescription) -> Relationship?{
72 | guard let inverseRelationship = obj.inverseRelationship else {
73 | return nil
74 | }
75 |
76 | if inverseRelationship.toMany == true{
77 | return mapManyToManyRelationship(obj)
78 | } else {
79 | return mapOneToManyRelationship(obj)
80 | }
81 | }
82 |
83 | func mapManyToManyRelationship(obj: NSRelationshipDescription) -> Relationship{
84 |
85 | let label = obj.name
86 | let entity = obj.entity
87 | let relationship = obj.inverseRelationship!
88 | let relatedEntity = relationship.entity
89 | let tableName = dottedNameToTableName(entity.managedObjectClassName)
90 | let relatedTableName = dottedNameToTableName(relatedEntity.managedObjectClassName)
91 | let tables = [tableName, relatedTableName]
92 | let throughModel : String?
93 |
94 | if let through = obj.userInfo?["throughModel"]{
95 | throughModel = through as? String
96 | } else{
97 | throughModel = nil
98 | }
99 |
100 | return ManyToMany(label, tables: tables, throughModel: throughModel)
101 | }
102 |
103 | func mapOneToManyRelationship(obj: NSRelationshipDescription) -> Relationship{
104 | let label = obj.name
105 | let relationship = obj.inverseRelationship!
106 | let entity = relationship.entity
107 | let tableName = dottedNameToTableName(entity.managedObjectClassName)
108 |
109 | return OneToMany(label, table: tableName, column: relationship.name)
110 | }
111 |
112 | func mapToOneRelationship(obj: NSRelationshipDescription) -> Column{
113 |
114 |
115 | let name = obj.name
116 | let entity = obj.destinationEntity!
117 | let destinationTable = dottedNameToTableName(entity.managedObjectClassName)
118 | let table = ORMModel.metadata.tables[destinationTable]!
119 |
120 | return Column(name, type: ForeignKey(table))
121 | }
122 | }
--------------------------------------------------------------------------------
/Amigo/ORMModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ORMModel.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/12/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func ==(lhs: ORMModel, rhs: ORMModel) -> Bool {
12 | return lhs.hashValue == rhs.hashValue
13 | }
14 |
15 |
16 | public class ORMModel: Hashable{
17 | public static let metadata = MetaData()
18 |
19 | public let table: Table
20 | public let foreignKeys: [String:Column]
21 | public let relationships: [String:Relationship]
22 | public let columns: [Column]
23 | public let primaryKey: Column!
24 | public let type: String
25 | public let label: String
26 | public var throughModelRelationship: ManyToMany?
27 | public var sqlInsert: String?
28 | public var sqlUpsert: String?
29 | public var sqlUpdate: String?
30 | public var sqlDelete: String?
31 | public var sqlDeleteThrough = [String: String]()
32 | public var sqlInsertThrough = [String: String]()
33 |
34 | public convenience init(_ qualifiedType: T.Type, _ properties: MetaItem...){
35 | let type = qualifiedType.description()
36 | self.init(type, properties: properties)
37 | }
38 |
39 | public convenience init(_ qualifiedType: String, properties: MetaItem...){
40 | self.init(qualifiedType, properties: properties)
41 | }
42 |
43 | public init(_ qualifiedType: String, properties:[MetaItem]){
44 |
45 | let schemaItems = properties.filter{$0 is SchemaItem}.map{ $0 as! SchemaItem}
46 |
47 | let relationshipList = properties.filter{$0 is Relationship}.map{ $0 as! Relationship }
48 | let nameParts = qualifiedType.unicodeScalars
49 | .split{ $0 == "." }
50 | .map{ String($0).lowercaseString }
51 |
52 | let tableName = nameParts.joinWithSeparator("_")
53 | var tmpForeignKeys = [String:Column]()
54 | var tmpColumns = [Column]()
55 | var tmpPrimaryKey: Column!
56 | var tmpRelationships = [String: Relationship]()
57 |
58 |
59 | relationshipList.forEach{ (each: Relationship) -> Void in
60 | if let m2m = each as? ManyToMany, let partial = m2m.partial{
61 | partial(type: qualifiedType)
62 | }
63 |
64 | if let o2m = each as? OneToMany{
65 | o2m.initOriginType(qualifiedType)
66 | }
67 | }
68 |
69 | type = qualifiedType
70 | label = nameParts[1]
71 |
72 | // as an ORMModel we do some fancy name mangling with foreignKey Columns:
73 | let transformedSchemaItems = schemaItems.map{ item -> SchemaItem in
74 |
75 | if let column = item as? Column, let fk = column.foreignKey{
76 | let associatedPrimaryKeyName = fk.relatedColumn.label
77 | // behold! the fancy name mangling
78 | let label = "\(column.label)_\(associatedPrimaryKeyName)"
79 |
80 | let primaryKey = column.primaryKey
81 | let indexed = column.indexed
82 | let unique = column.unique
83 | let defaultValue = column.defaultValue
84 |
85 | let newColumn = Column(label, type: fk, primaryKey: primaryKey, indexed: indexed, unique: unique, defaultValue: defaultValue)
86 | tmpForeignKeys[column.label] = newColumn
87 |
88 | return newColumn as SchemaItem
89 | }
90 |
91 | return item
92 | }
93 |
94 | table = Table(tableName, metadata: ORMModel.metadata, items: transformedSchemaItems)
95 | relationshipList.forEach{tmpRelationships[$0.label] = $0}
96 |
97 | table.sortedColumns.forEach{ value -> () in
98 |
99 | guard value.foreignKey == nil else {
100 | return
101 | }
102 |
103 | tmpColumns.append(value)
104 |
105 | if value.primaryKey {
106 | tmpPrimaryKey = value
107 | }
108 | }
109 |
110 | foreignKeys = tmpForeignKeys
111 | columns = tmpColumns
112 | primaryKey = tmpPrimaryKey
113 | relationships = tmpRelationships
114 |
115 | AmigoModel.amigoModelIndex[qualifiedType] = self
116 | table.model = self
117 | }
118 |
119 | public var hashValue: Int{
120 | return type.hashValue
121 | }
122 | }
--------------------------------------------------------------------------------
/Amigo/AmigoSessionModelAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoSessionModelAction.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/16/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | /// Used in Many-To-Many queries
13 | public class AmigoSessionModelAction{
14 | let using: T
15 | let usingModel: ORMModel
16 | let session: AmigoSession
17 | var _relationship: String?
18 |
19 | public init(_ obj: T, model: ORMModel, session: AmigoSession){
20 | self.using = obj
21 | self.usingModel = model
22 | self.session = session
23 | }
24 |
25 | public func relationship(value: String) -> AmigoSessionModelAction{
26 | self._relationship = value
27 | return self
28 | }
29 |
30 | public func delete(other: U){
31 |
32 | if let key = _relationship{
33 |
34 | if let relationship = usingModel.relationships[key] as? ManyToMany{
35 |
36 | if let throughModel = relationship.throughModel{
37 | fatalError("Relationship is managed though: \(throughModel)")
38 | }
39 |
40 | let leftModel = session.config.tableIndex[relationship.tables[0]]!
41 | let rightModel = session.config.tableIndex[relationship.tables[1]]!
42 | let left: AmigoModel
43 | let right: AmigoModel
44 |
45 | if leftModel == usingModel{
46 | left = using
47 | right = other
48 | } else {
49 | left = other
50 | right = using
51 | }
52 |
53 | let leftId = leftModel.primaryKey!.label
54 | let leftColumn = "\(leftModel.label)_\(leftId)"
55 | let leftParam = left.valueForKey(leftId)!
56 |
57 | let rightId = rightModel.primaryKey!.label
58 | let rightColumn = "\(rightModel.label)_\(rightId)"
59 | let rightParam = right.valueForKey(rightId)!
60 |
61 | var delete = relationship.associationTable.delete()
62 |
63 | let predicate = NSPredicate(format:" \(leftColumn) = \"\(leftParam)\" AND \(rightColumn) = \"\(rightParam)\"")
64 |
65 | let (filter, params) = session.engine.compiler.compile(predicate,
66 | table: relationship.associationTable,
67 | models: session.config.typeIndex)
68 |
69 | delete.filter(filter)
70 |
71 | let sql = session.engine.compiler.compile(delete)
72 | session.engine.execute(sql, params: params)
73 | }
74 | }
75 | }
76 |
77 | public func add(other: U...){
78 | add(other)
79 | }
80 |
81 | public func add(other: [U]){
82 | other.forEach(addModel)
83 | }
84 |
85 | public func addModel(other: U){
86 |
87 | if let key = _relationship{
88 |
89 | if let relationship = usingModel.relationships[key] as? ManyToMany{
90 |
91 | if let throughModel = relationship.throughModel{
92 | fatalError("Relationship is managed though: \(throughModel)")
93 | }
94 |
95 | let leftModel = session.config.tableIndex[relationship.tables[0]]!
96 | let rightModel = session.config.tableIndex[relationship.tables[1]]!
97 | let left: AmigoModel
98 | let right: AmigoModel
99 |
100 | if leftModel == usingModel{
101 | left = using
102 | right = other
103 | } else {
104 | left = other
105 | right = using
106 | }
107 |
108 | let leftId = leftModel.primaryKey!.label
109 | let leftParam = left.valueForKey(leftId)!
110 |
111 | let rightId = rightModel.primaryKey!.label
112 | let rightParam = right.valueForKey(rightId)!
113 |
114 | let params = [leftParam, rightParam]
115 | let insert = relationship.associationTable.insert()
116 | let sql = session.engine.compiler.compile(insert)
117 |
118 | session.engine.execute(sql, params: params)
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Amigo/AmigoMetaData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoMeta.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/1/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | public class AmigoMetaData{
14 | var tables = [String:MetaModel]()
15 |
16 | public init(_ managedObjectModel: NSManagedObjectModel){
17 | self.initialize(managedObjectModel)
18 | }
19 |
20 | public func metaModelForType(type: T.Type) -> MetaModel{
21 | let key = type.description()
22 | return metaModelForName(key)
23 | }
24 |
25 | public func metaModelForName(name: String) -> MetaModel{
26 | return tables[name]!
27 | }
28 |
29 | func initialize(managedObjectModel: NSManagedObjectModel){
30 | let lookup = managedObjectModel.entitiesByName
31 | // there may be a better way here, right now we ensure we have
32 | // all the models, then we need to go do some additional passes
33 | // to ensure the ForeignKey Relationships (ToOne) and the Many To Many
34 | // relationships (ToMany).
35 | //
36 | // the N*3 loops hurts my heart, but for now, it'a only done
37 | // on initialization.
38 |
39 |
40 | lookup.map{metaModelFromEntity($1)}
41 | zip(lookup.values, tables.values).map(initializeRelationships)
42 |
43 | // lookup.map{registerModel($1)}
44 |
45 |
46 | // zip(lookup.values, tables.values).map(initializeRelationships)
47 | // initializeRelationships($0, model: $1)
48 | // }
49 |
50 | // join.map{(x, y) -> String in
51 | // print(99)
52 | // return ""
53 | // }.count
54 |
55 |
56 | }
57 |
58 | func initializeRelationships(entity:NSEntityDescription, model: MetaModel){
59 |
60 | var columns = [Column]()
61 | var foreignKeys = [ForeignKey]()
62 |
63 | // sqlite supports FK's with actions:
64 | // https://www.sqlite.org/foreignkeys.html#fk_actions
65 | // TODO determine if iOS 9+ sqlite supports this
66 | // adjust column creation accordingly
67 |
68 | entity.relationshipsByName
69 | .filter{ $1.toMany == false }
70 | .map{ _, relationship -> Void in
71 |
72 | let target = relationship.destinationEntity!
73 | let targetModel = metaModelForName(target.managedObjectClassName)
74 |
75 | let foreignKey = ForeignKey(
76 | label: relationship.name,
77 | type: targetModel.primaryKey.type,
78 | relatedType: targetModel.table.type,
79 | optional: relationship.optional)
80 |
81 | let column = Column(
82 | label: "\(relationship.name)_id",
83 | type: targetModel.primaryKey.type,
84 | primaryKey: false,
85 | indexed: true,
86 | optional: relationship.optional,
87 | unique: false,
88 | foreignKey: foreignKey)
89 |
90 | columns.append(column)
91 | foreignKeys.append(foreignKey)
92 | }
93 |
94 | let model = MetaModel(
95 | table: model.table,
96 | columns: model.columns + columns,
97 | primaryKey: model.primaryKey,
98 | foreignKeys: foreignKeys)
99 |
100 | tables[String(model.table.type)] = model
101 |
102 | }
103 |
104 |
105 | func registerModel(model: MetaModel){
106 |
107 | }
108 |
109 | func metaModelFromEntity(entityDescription: NSEntityDescription){
110 | let model: MetaModel
111 | var primaryKey: Column?
112 | let table = Table.fromEntityDescription(entityDescription)
113 | var columns = entityDescription.attributesByName.map {
114 | Column.fromAttributeDescription($1)
115 | }
116 |
117 | primaryKey = columns.filter{ $0.primaryKey }.first
118 | if primaryKey == nil{
119 | primaryKey = Column.defaultPrimaryKey()
120 | columns = [primaryKey!] + columns
121 | }
122 |
123 | model = MetaModel(table: table, columns: columns, primaryKey: primaryKey!)
124 | tables[String(table.type)] = model
125 | }
126 |
127 | func createAll(engine: Engine){
128 |
129 | }
130 | }
--------------------------------------------------------------------------------
/AmigoTests/MetaColumnTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetaColumnTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/6/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 | class MetaColumnTests: XCTestCase {
14 |
15 |
16 | func testString() {
17 | let column = Column("test", type: String.self)
18 | XCTAssertTrue(column.type == NSAttributeType.StringAttributeType)
19 | }
20 |
21 | func testInt16() {
22 | let column = Column("test", type: Int16.self)
23 | XCTAssertTrue(column.type == NSAttributeType.Integer16AttributeType)
24 | }
25 |
26 | func testInt32() {
27 | let column = Column("test", type: Int32.self)
28 | XCTAssertTrue(column.type == NSAttributeType.Integer32AttributeType)
29 | }
30 |
31 | func testInt64() {
32 | let column = Column("test", type: Int64.self)
33 | XCTAssertTrue(column.type == NSAttributeType.Integer64AttributeType)
34 | }
35 |
36 | func testInt() {
37 | let column = Column("test", type: Int.self)
38 | XCTAssertTrue(column.type == NSAttributeType.Integer64AttributeType)
39 | }
40 |
41 | func testBool() {
42 | let column = Column("test", type: Bool.self)
43 | XCTAssertTrue(column.type == NSAttributeType.BooleanAttributeType)
44 | }
45 |
46 | func testDouble() {
47 | let column = Column("test", type: Double.self)
48 | XCTAssertTrue(column.type == NSAttributeType.DoubleAttributeType)
49 | }
50 |
51 | func testFloat() {
52 | let column = Column("test", type: Float.self)
53 | XCTAssertTrue(column.type == NSAttributeType.FloatAttributeType)
54 | }
55 |
56 | func testUInt8Buffer() {
57 | let column = Column("test", type: [UInt8].self)
58 | XCTAssertTrue(column.type == NSAttributeType.BinaryDataAttributeType)
59 | }
60 |
61 | func testNSData() {
62 | let column = Column("test", type: NSData.self)
63 | XCTAssertTrue(column.type == NSAttributeType.BinaryDataAttributeType)
64 | }
65 |
66 | func testNSDate() {
67 | let column = Column("test", type: NSDate.self)
68 | XCTAssertTrue(column.type == NSAttributeType.DateAttributeType)
69 | }
70 |
71 | func testNSString() {
72 | let column = Column("test", type: NSString.self)
73 | XCTAssertTrue(column.type == NSAttributeType.StringAttributeType)
74 | }
75 |
76 | func testDecimalNumber() {
77 | let column = Column("test", type: NSDecimalNumber.self)
78 | XCTAssertTrue(column.type == NSAttributeType.DecimalAttributeType)
79 | }
80 |
81 | func testNoDefaultValueWithNil() {
82 | let column = Column("test", type: String.self)
83 |
84 | let value = column.valueOrDefault(nil)
85 | XCTAssert(value == nil)
86 | }
87 |
88 | func testNoDefaultValueWithValue() {
89 | let column = Column("test", type: String.self)
90 |
91 | let value = column.valueOrDefault("ollie") as! String
92 | XCTAssert(value == "ollie")
93 | }
94 |
95 | func testDefaultValue() {
96 | let column = Column("test", type: String.self){
97 | return "ollie"
98 | }
99 |
100 | let value = column.valueOrDefault(nil) as! String
101 | XCTAssert(value == "ollie")
102 | }
103 |
104 | func testDefaultValueSkip() {
105 | let column = Column("test", type: String.self){
106 | return "ollie"
107 | }
108 |
109 | let value = column.valueOrDefault("lucy") as! String
110 | XCTAssert(value == "lucy")
111 | }
112 |
113 | func testForeignKeyColumn() {
114 | let meta = MetaData()
115 | let t1 = Table("dogs", metadata: meta,
116 | Column("id", type: Int.self, primaryKey: true),
117 | Column("label", type: String.self)
118 | )
119 |
120 | XCTAssert(t1.c["id"]!.table != nil)
121 |
122 | let t2 = Table("owner", metadata: meta,
123 | Column("id", type: Int.self, primaryKey: true),
124 | Column("label", type: String.self),
125 | Column("dog_id", type: ForeignKey(t1.c["id"]!))
126 | )
127 |
128 | XCTAssertTrue(t2.c["dog_id"]!.type == t1.primaryKey!.type)
129 | }
130 |
131 | func testForeignKeyTable() {
132 | let meta = MetaData()
133 | let t1 = Table("dogs", metadata: meta,
134 | Column("id", type: Int.self, primaryKey: true),
135 | Column("label", type: String.self)
136 | )
137 |
138 | XCTAssert(t1.c["id"]!.table != nil)
139 |
140 | let t2 = Table("owner", metadata: meta,
141 | Column("id", type: Int.self, primaryKey: true),
142 | Column("label", type: String.self),
143 | Column("dog_id", type: ForeignKey(t1))
144 | )
145 |
146 | XCTAssertTrue(t2.c["dog_id"]!.type == t1.primaryKey!.type)
147 | }
148 |
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/Amigo/Column.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Column.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/3/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public func ==(lhs: Column, rhs: Column) -> Bool {
13 | return lhs.hashValue == rhs.hashValue
14 | }
15 |
16 |
17 | public class Column: SchemaItem, CustomStringConvertible, Hashable{
18 |
19 | public let label: String
20 | public let type: NSAttributeType
21 | public let primaryKey: Bool
22 | public let indexed: Bool
23 | public let unique: Bool
24 | public var optional: Bool
25 | public let defaultValue: (() -> AnyObject?)?
26 |
27 | public var hashValue: Int{
28 | return description.hashValue
29 | }
30 |
31 | public func modelValue(model: AmigoModel) -> AnyObject? {
32 | return model.valueForKey(label)
33 | }
34 |
35 | public func valueOrDefault(model: AmigoModel) -> AnyObject? {
36 | return valueOrDefault(modelValue(model))
37 | }
38 |
39 | public func valueOrDefault(value: AnyObject?) -> AnyObject? {
40 | if let defaultValue = defaultValue where value == nil{
41 | return defaultValue()
42 | }
43 |
44 | return value
45 | }
46 |
47 | public func serialize(value: AnyObject?) -> AnyObject? {
48 | return value
49 | }
50 |
51 | public func deserialize(value: AnyObject?) -> AnyObject? {
52 | return value
53 | }
54 |
55 | var _foreignKey: ForeignKey?
56 | public var foreignKey: ForeignKey? {
57 | get{
58 | return _foreignKey
59 | }
60 | }
61 |
62 | var _table: Table? {
63 | didSet{
64 | _qualifiedLabel = "\(_table!.label).\(label)"
65 | }
66 | }
67 |
68 | public var table: Table? {
69 | return _table
70 | }
71 |
72 | var _qualifiedLabel: String?
73 | public var qualifiedLabel: String? {
74 | return _qualifiedLabel
75 | }
76 |
77 | public init(_ label: String, type: NSAttributeType, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil){
78 | self.label = label
79 | self.type = type
80 | self.primaryKey = primaryKey
81 | self.indexed = indexed
82 | self.optional = optional
83 | self.unique = unique
84 | self.defaultValue = defaultValue
85 | }
86 |
87 | public convenience init(_ label: String, type: Any.Type, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil){
88 | let attrType: NSAttributeType
89 |
90 | switch type{
91 | case is NSString.Type:
92 | attrType = .StringAttributeType
93 | case is String.Type:
94 | attrType = .StringAttributeType
95 | case is Int16.Type:
96 | attrType = .Integer16AttributeType
97 | case is Int32.Type:
98 | attrType = .Integer32AttributeType
99 | case is Int64.Type:
100 | attrType = .Integer64AttributeType
101 | case is Int.Type:
102 | attrType = .Integer64AttributeType
103 | case is NSDate.Type:
104 | attrType = .DateAttributeType
105 | case is [UInt8].Type:
106 | attrType = .BinaryDataAttributeType
107 | case is NSData.Type:
108 | attrType = .BinaryDataAttributeType
109 | case is NSDecimalNumber.Type:
110 | attrType = .DecimalAttributeType
111 | case is Double.Type:
112 | attrType = .DoubleAttributeType
113 | case is Float.Type:
114 | attrType = .FloatAttributeType
115 | case is Bool.Type:
116 | attrType = .BooleanAttributeType
117 | default:
118 | attrType = .UndefinedAttributeType
119 | }
120 |
121 | self.init(label, type: attrType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
122 |
123 | }
124 |
125 | public convenience init(_ label: String, type: ForeignKey, primaryKey: Bool = false, indexed: Bool = true, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil){
126 | let associatedType = type.relatedColumn.type
127 | self.init(label, type: associatedType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
128 | _foreignKey = ForeignKey(type.relatedColumn, column: self)
129 | }
130 |
131 |
132 | public var description: String {
133 | if let t = table{
134 | return ":\(label), primaryKey:\(primaryKey), indexed: \(indexed), optional: \(optional), unique:\(unique)>"
135 | } else {
136 | return ""
137 | }
138 |
139 | }
140 | }
--------------------------------------------------------------------------------
/Amigo/SQLiteEngine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SQLiteEngine.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/2/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 |
13 |
14 | public class SQLiteEngine: NSObject, Engine{
15 | public let fetchLastRowIdAfterInsert = true
16 | public let _compiler = SQLiteCompiler()
17 | public var db: FMDatabaseQueue
18 | public let path: String?
19 |
20 | let echo: Bool
21 | var savepoints = [String]()
22 |
23 | public var compiler: Compiler{
24 | return _compiler
25 | }
26 |
27 | public init(_ path: String?, echo: Bool = false){
28 | self.path = path
29 | self.echo = echo
30 |
31 | if path == ":memory:" || path == nil {
32 | db = FMDatabaseQueue(path: nil)
33 | } else{
34 | db = FMDatabaseQueue(path: path)
35 | }
36 | }
37 |
38 | public func createBatchOperation(session: AmigoSession) -> AmigoBatchOperation{
39 | return SQLiteBatchOperation(session: session)
40 | }
41 |
42 |
43 | public func beginTransaction(){
44 | var inTransaction: Bool = false
45 | db.inDatabase{ inTransaction = $0.inTransaction() }
46 |
47 | if inTransaction {
48 | let savepoint = "amigo.sqllite.\(savepoints.count)"
49 | savepoints.append(savepoint)
50 |
51 | if self.echo{
52 | self.echo("SAVEPOINT", params: [savepoint])
53 | }
54 |
55 | db.inDatabase{
56 | do{
57 | try $0.startSavePointWithName(savepoint)
58 | } catch _ {
59 | debugPrint("failed to start savepoint: \(savepoint)")
60 | }
61 | }
62 | } else {
63 | if self.echo{
64 | self.echo("BEGIN TRANSACTION", params: nil)
65 | }
66 |
67 | db.inDatabase{$0.beginTransaction()}
68 | }
69 | }
70 |
71 | public func commitTransaction(){
72 |
73 | if savepoints.count > 0{
74 | let savepoint = savepoints.removeLast()
75 | db.inDatabase{
76 | do {
77 | try $0.releaseSavePointWithName(savepoint)
78 | } catch _ {
79 | debugPrint("unable to release savepoint: \(savepoint)")
80 | }
81 | }
82 |
83 | } else {
84 |
85 | if self.echo{
86 | self.echo("COMMIT TRANSACTION", params: nil)
87 | }
88 | db.inDatabase{$0.commit()}
89 | }
90 | }
91 |
92 | public func rollback(){
93 | if savepoints.count > 0{
94 | let savepoint = savepoints.removeLast()
95 |
96 | if self.echo{
97 | self.echo("ROLLBACK TO SAVEPOINT", params: [savepoint])
98 | }
99 |
100 | db.inDatabase{
101 | do {
102 | try $0.rollbackToSavePointWithName(savepoint)
103 | } catch _ {
104 | debugPrint("unable to rollback to savepoint: \(savepoint)")
105 | }
106 |
107 | }
108 | } else {
109 |
110 | if self.echo{
111 | self.echo("ROLLBACK TRANSACTION", params: nil)
112 | }
113 | db.inDatabase{$0.rollback()}
114 | }
115 | }
116 |
117 | public func lastrowid() -> Int{
118 |
119 | let result = execute("SELECT last_insert_rowid();"){ (results: FMResultSet) -> Int in
120 | results.next()
121 |
122 | let index = Int(results.intForColumnIndex(0))
123 | results.close()
124 | return index
125 | }
126 |
127 | return result
128 | }
129 |
130 | func echo(sql: String, params:[AnyObject]! = nil){
131 | print("------------------")
132 | print("[PARAMS] \(params)")
133 | print("[SQL] \(sql)")
134 | print("------------------")
135 | }
136 |
137 | public func execute(sql: String, params: [AnyObject]! = nil, mapper: Input -> Output) -> Output{
138 | var output: Output?
139 |
140 | db.inDatabase{ db in
141 |
142 | if self.echo{
143 | self.echo(sql, params: params)
144 | }
145 |
146 | let input = db.executeQuery(sql, withArgumentsInArray: params) as! Input
147 | output = mapper(input)
148 | }
149 |
150 | return output!
151 | }
152 |
153 |
154 | public func execute(sql: String){
155 | db.inDatabase{ db in
156 |
157 | if self.echo{
158 | self.echo(sql)
159 | }
160 |
161 | db.executeStatements(sql)
162 | }
163 | }
164 |
165 | public func execute(sql: String, params: [AnyObject]! = nil){
166 |
167 | db.inDatabase{ db in
168 |
169 | if self.echo{
170 | self.echo(sql, params: params)
171 | }
172 |
173 | db.executeUpdate(sql, withArgumentsInArray: params)
174 | }
175 |
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/docs/setup/index.rst:
--------------------------------------------------------------------------------
1 | Setup
2 | =================================
3 |
4 |
5 | Carthage
6 | ----------------------------------
7 |
8 | FMDB (https://github.com/ccgus/fmdb),
9 | as of v 2.6 FMDB, does suport Carthage.
10 |
11 | Drop this into your :code:`Cartfile`:
12 |
13 | ::
14 |
15 | github "blitzagency/amigo-swift" ~> 0.3.1
16 |
17 | Admittedly, we are probably not the best at the whole,
18 | *"How do you share an Xcode Project"* thing, so any recommendations
19 | to imporve this process are welcome.
20 |
21 | Initialization Using A Closure
22 | ----------------------------------
23 |
24 | Initialize Amigo into a global variable so all the initialization
25 | is done in one place:
26 |
27 | .. code-block:: swift
28 |
29 | import Amigo
30 |
31 |
32 | class Dog: AmigoModel{
33 | dynamic var id = 0
34 | dynamic var label = ""
35 | }
36 |
37 | let amigo: Amigo = {
38 | let dog = ORMModel(Dog.self,
39 | IntegerField("id", primaryKey: true),
40 | CharField("label")
41 | )
42 |
43 | // now initialize Amigo
44 | // specifying 'echo: true' will have amigo print out
45 | // all of the SQL commands it's generating.
46 | let engine = SQLiteEngineFactory(":memory:", echo: true)
47 | let amigo = Amigo([dog], factory: engine)
48 | amigo.createAll()
49 |
50 | return amigo
51 | }()
52 |
53 |
54 | .. note::
55 |
56 | This creates the :code:`amigo` object lazily, which means it's not
57 | created until it's actually needed. This delays the initial
58 | output of the app information details. Because of this,
59 | we recommend forcing the :code:`amigo` object to be created
60 | at app launch by just referencing :code:`amigo` at the top of
61 | your :code:`didFinishLaunching` method if you don't
62 | already use the :code:`amigo` object for something on app launch.
63 | This style and description was taken directly from [XCGLogger]_
64 |
65 |
66 | A Note About Threads and Async
67 | -------------------------------
68 |
69 | Out of the box, Amigo uses FMDB's :code:`FMDatabaseQueue` to perform all
70 | of it work. This should set you up to user Amigo from any thread you like.
71 |
72 | Keep in mind though, the dispatch queue that :code:`FMDatabasesQueue` uses
73 | is a serial queue. So if you invoke multiple long running amigo commands
74 | from separate threads you will be waiting your turn in the serial queue
75 | before your command is executed. It's probably best to:
76 |
77 | .. code-block:: swift
78 |
79 | func doStuff(callback: ([YourModelType]) -> ()){
80 | let session = amigo.session
81 | // any background queue you like
82 | let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
83 |
84 | disptatch_async(queue) {
85 | let results = session.query(YourModelType).all() // whatever amigo command you want
86 | dispatch_async(dispatch_get_main_queue()){
87 | callback(results)
88 | }
89 | }
90 | }
91 |
92 | If that's too verbose for your liking you are also welcome to use the
93 | :code:`async` convenience methods provided by the amigo session. The
94 | above code would then look like this:
95 |
96 | .. code-block:: swift
97 |
98 | func doStuff(callback: ([YourModelType]) -> ()){
99 | let session = amigo.session
100 | session.async{ () -> [YourModelType] in
101 | let results = session.query(YourModelType).all()
102 | return results
103 | }.then(callback)
104 | }
105 |
106 | There are a few ways to use these async handlers. The variations revolve
107 | around weather or not you are returning results. Check out the unit tests
108 | [AmigoSessionAsyncTests]_ for more examples.
109 |
110 | For example, you don't have to return any result at all:
111 |
112 | .. code-block:: swift
113 |
114 | func addObject(){
115 | let session = amigo.session
116 | session.async{
117 | let dog = Dog()
118 | dog.label = "Lucy"
119 | session.add(dog)
120 | }
121 | }
122 |
123 | func addBatch(){
124 | let session = amigo.session
125 | session.async{
126 |
127 | let d1 = Dog()
128 | d1.label = "Lucy"
129 |
130 | let d2 = Dog()
131 | d2.label = "Ollie"
132 |
133 | session.batch{ batch in
134 | batch.add([d1, d2])
135 | }
136 | }
137 | }
138 |
139 | You can also specify your own background queue to execute on:
140 |
141 | .. code-block:: swift
142 |
143 | let queue = dispatch_queue_create("com.amigo.async.tests", nil)
144 |
145 | func addDoStuffOnMyOwnQueue(){
146 | let session = amigo.session
147 | session.async(queue: queue){
148 | let dog = Dog()
149 | dog.label = "Lucy"
150 | session.add(dog)
151 | }
152 | }
153 |
154 |
155 | Contents:
156 |
157 | .. toctree::
158 | :maxdepth: 2
159 |
160 | engine
161 |
162 |
163 | .. [XCGLogger] XCGLogger Closure Initialization
164 | https://github.com/DaveWoodCom/XCGLogger#initialization-using-a-closure
165 |
166 | .. [AmigoSessionAsyncTests] AmigoSessionAsyncTests
167 | https://github.com/blitzagency/amigo-swift/blob/master/AmigoTests/AmigoSessionAsyncTests.swift
168 |
--------------------------------------------------------------------------------
/Amigo/Fields.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Fields.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 1/3/16.
6 | // Copyright © 2016 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class UUIDField: Column{
12 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
13 | self.init(label, type: .BinaryDataAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
14 | }
15 |
16 | public override func serialize(value: AnyObject?) -> AnyObject?{
17 |
18 | guard let string = value as? String,
19 | let uuid = NSUUID(UUIDString: string) else {
20 | return nil
21 | }
22 |
23 | var bytes = [UInt8](count: 16, repeatedValue: 0)
24 | uuid.getUUIDBytes(&bytes)
25 |
26 | return NSData(bytes: bytes, length: bytes.count)
27 | }
28 |
29 | /// Deserializes `UUID` Bytes back to their `String` representation
30 | ///
31 | /// - Attention:
32 | ///
33 | /// Each field is treated as an integer and has its value printed as a
34 | /// zero-filled hexadecimal digit string with the most significant
35 | /// digit first. The hexadecimal values "a" through "f" are output as
36 | /// lower case characters and are case insensitive on input.
37 | ///
38 | /// RFC 4122 specifies output as lower case characters
39 | /// when NSUUID decodes the bytes back into the string it
40 | /// keeps them as upper case. We intentionally force
41 | /// the values back to lower case to keep in line with the RFC
42 | ///
43 | /// - SeeAlso:
44 | ///
45 | /// RFC 4122 (https://www.ietf.org/rfc/rfc4122.txt)
46 | ///
47 | /// Declaration of syntactic structure
48 | ///
49 | public override func deserialize(value: AnyObject?) -> AnyObject?{
50 | guard let value = value as? NSData else {
51 | return nil
52 | }
53 |
54 | var bytes = [UInt8](count: 16, repeatedValue: 0)
55 | value.getBytes(&bytes, length: bytes.count)
56 |
57 | let uuid = NSUUID(UUIDBytes: bytes).UUIDString.lowercaseString
58 | return uuid
59 | }
60 | }
61 |
62 |
63 | public class CharField: Column{
64 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
65 | self.init(label, type: .StringAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
66 | }
67 | }
68 |
69 |
70 | public class BooleanField: Column{
71 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
72 | self.init(label, type: .BooleanAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
73 | }
74 | }
75 |
76 |
77 | public class IntegerField: Column{
78 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
79 | self.init(label, type: .Integer64AttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
80 | }
81 |
82 | override public func modelValue(model: AmigoModel) -> AnyObject? {
83 | let value = model.valueForKey(label)
84 |
85 | if let value = value as? Int where value == 0 && primaryKey == true{
86 | return nil
87 | }
88 |
89 | return value
90 | }
91 | }
92 |
93 |
94 | public class FloatField: Column{
95 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
96 | self.init(label, type: .FloatAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
97 | }
98 | }
99 |
100 |
101 | public class DoubleField: Column{
102 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
103 | self.init(label, type: .DoubleAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
104 | }
105 | }
106 |
107 |
108 | public class BinaryField: Column{
109 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
110 | self.init(label, type: .BinaryDataAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
111 | }
112 | }
113 |
114 |
115 | public class DateTimeField: Column{
116 | public convenience init(_ label: String, primaryKey: Bool = false, indexed: Bool = false, optional: Bool = true, unique: Bool = false, defaultValue: (()-> AnyObject?)? = nil) {
117 | self.init(label, type: .DateAttributeType, primaryKey: primaryKey, indexed: indexed, optional: optional, unique: unique, defaultValue: defaultValue)
118 | }
119 | }
--------------------------------------------------------------------------------
/AmigoTests/ModelMappingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModelMappingTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/31/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Amigo
11 |
12 |
13 | class ModelMappingTests: XCTestCase {
14 | var amigo: Amigo!
15 |
16 | override func setUp() {
17 | super.setUp()
18 | }
19 |
20 | override func tearDown() {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | super.tearDown()
23 | }
24 |
25 | func testOneToMany() {
26 | let dog = ORMModel(Dog.self,
27 | Column("id", type: Int.self, primaryKey: true),
28 | Column("label", type: String.self),
29 | OneToMany("people", using: People.self)
30 | )
31 |
32 | let people = ORMModel(People.self,
33 | Column("id", type: Int.self, primaryKey: true),
34 | Column("label", type: String.self),
35 | Column("dog", type: ForeignKey(dog))
36 | )
37 |
38 | let engine = SQLiteEngineFactory(":memory:", echo: true)
39 | amigo = Amigo([dog, people], factory: engine)
40 | amigo.createAll()
41 |
42 | let session = amigo.session
43 |
44 | let d1 = Dog()
45 | d1.label = "Lucy"
46 |
47 | let p1 = People()
48 | p1.label = "Foo"
49 | p1.dog = d1
50 |
51 | let p2 = People()
52 | p2.label = "Bar"
53 | p2.dog = d1
54 |
55 | session.add([d1, p1, p2])
56 |
57 | let results = session
58 | .query(People)
59 | .using(d1)
60 | .relationship("people")
61 | .all()
62 |
63 | XCTAssertEqual(results.count, 2)
64 |
65 | }
66 |
67 | func testManyToMany() {
68 | let parent = ORMModel(Parent.self,
69 | Column("id", type: Int.self, primaryKey: true),
70 | Column("label", type: String.self),
71 | ManyToMany("children", using: Child.self)
72 | )
73 |
74 | let child = ORMModel(Child.self,
75 | Column("id", type: Int.self, primaryKey: true),
76 | Column("label", type: String.self),
77 |
78 | // if you would like to act on the inverse
79 | // define it as well.
80 | ManyToMany("parents", using: Parent.self)
81 | )
82 |
83 | let engine = SQLiteEngineFactory(":memory:", echo: true)
84 | amigo = Amigo([parent, child], factory: engine)
85 | amigo.createAll()
86 |
87 | let session = amigo.session
88 |
89 | let p1 = Parent()
90 | p1.label = "Foo"
91 |
92 | let p2 = Parent()
93 | p2.label = "Foo"
94 |
95 | let c1 = Child()
96 | c1.label = "Baz"
97 |
98 | let c2 = Child()
99 | c2.label = "Qux"
100 |
101 | session.add([p1, p2, c1, c2])
102 |
103 | // add 2 children to p1
104 | session.using(p1).relationship("children").add(c1, c2)
105 |
106 | var results = session
107 | .query(Child)
108 | .using(p1)
109 | .relationship("children")
110 | .all()
111 |
112 | XCTAssertEqual(results.count, 2)
113 |
114 | results = session
115 | .query(Child)
116 | .using(p2)
117 | .relationship("children")
118 | .all()
119 |
120 | XCTAssertEqual(results.count, 0)
121 |
122 | // add a shared parent to the children using
123 | // the inverse relationship to ensure it works as well
124 | session.using(c1).relationship("parents").add(p2)
125 | session.using(c2).relationship("parents").add(p2)
126 |
127 |
128 | results = session
129 | .query(Child)
130 | .using(p2)
131 | .relationship("children")
132 | .all()
133 |
134 | XCTAssertEqual(results.count, 2)
135 |
136 | }
137 |
138 | func testManyToManyThrough() {
139 | let workout = ORMModel(Workout.self,
140 | Column("id", type: Int.self, primaryKey: true),
141 | Column("label", type: String.self),
142 | ManyToMany("exercises", using: WorkoutExercise.self, throughModel: WorkoutMeta.self)
143 | )
144 |
145 | let workoutExercise = ORMModel(WorkoutExercise.self,
146 | Column("id", type: Int.self, primaryKey: true),
147 | Column("label", type: String.self)
148 | )
149 |
150 | let workoutMeta = ORMModel(WorkoutMeta.self,
151 | Column("id", type: Int.self, primaryKey: true),
152 | Column("duration", type: Int.self),
153 | Column("position", type: Int.self),
154 | Column("exercise", type: ForeignKey(workoutExercise)),
155 | Column("workout", type: ForeignKey(workout))
156 | )
157 |
158 | let engine = SQLiteEngineFactory(":memory:", echo: true)
159 | amigo = Amigo([workout, workoutExercise, workoutMeta], factory: engine)
160 | amigo.createAll()
161 |
162 | let session = amigo.session
163 |
164 | let w1 = Workout()
165 | w1.label = "foo"
166 |
167 | let e1 = WorkoutExercise()
168 | e1.label = "Jumping Jacks"
169 |
170 | let m1 = WorkoutMeta()
171 | m1.workout = w1
172 | m1.exercise = e1
173 | m1.duration = 60000
174 | m1.position = 1
175 |
176 | session.add([w1, e1, m1])
177 |
178 |
179 | let results = session
180 | .query(WorkoutMeta)
181 | .using(w1)
182 | .relationship("exercises")
183 | .orderBy("position", ascending: true)
184 | .all()
185 |
186 | XCTAssertEqual(results.count, 1)
187 |
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/Amigo.xcodeproj/xcshareddata/xcschemes/Amigo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
44 |
46 |
47 |
49 |
50 |
52 |
53 |
55 |
56 |
58 |
59 |
61 |
62 |
64 |
65 |
67 |
68 |
70 |
71 |
73 |
74 |
76 |
77 |
78 |
79 |
80 |
81 |
87 |
88 |
89 |
90 |
91 |
92 |
102 |
103 |
109 |
110 |
111 |
112 |
115 |
116 |
117 |
118 |
119 |
120 |
126 |
127 |
133 |
134 |
135 |
136 |
138 |
139 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/docs/setup/orm_models.rst:
--------------------------------------------------------------------------------
1 | ORM Model Mapping
2 | ===================================
3 |
4 |
5 | Amigo can parse a :code:`NSManagedObjectModel` but all it's doing is
6 | converting the :code:`NSEntityDescriptions` into :code:`ORMModel`
7 | instances. Lets take a look at how we do that.
8 |
9 |
10 | .. important::
11 | When performing a model mapping your data models **MUST**
12 | inherit from :code:`Amigo.AmigoModel`
13 |
14 |
15 | .. code-block:: swift
16 |
17 | import Amigo
18 |
19 | class Dog: AmigoModel{
20 | dynamic var id: NSNumber!
21 | dynamic var label: String!
22 | }
23 |
24 | let dog = ORMModel(Dog.self,
25 | Column("id", type: Int.self, primaryKey: true)
26 | Column("label", type: String.self)
27 | Index("dog_label_idx", "label")
28 | )
29 |
30 | // you could achieve the same mapping this way:
31 |
32 | let dog = ORMModel(Dog.self,
33 | Column("id", type: Int.self, primaryKey: true)
34 | Column("label", type: String.self, indexed: true)
35 | )
36 |
37 | // now initialize Amigo
38 | // specifying 'echo: true' will have amigo print out
39 | // all of the SQL commands it's generating.
40 | let engine = SQLiteEngineFactory(":memory:", echo: true)
41 | amigo = Amigo([dog], factory: engine)
42 | amigo.createAll()
43 |
44 |
45 | Column Options
46 | ------------------------
47 |
48 | Columns can be initialized with the following options (default values presented):
49 |
50 | .. code-block:: swift
51 |
52 | type: // See Column Types below
53 | primaryKey: Bool = false
54 | indexed: Bool = false
55 | optional: Bool = true
56 | unique: Bool = false
57 |
58 |
59 | Column Types
60 | ------------------------
61 |
62 | Your avavilable options for `Column` types are as follows:
63 |
64 | .. code-block:: swift
65 |
66 | NSString
67 | String
68 | Int16
69 | Int32
70 | Int64
71 | Int
72 | NSDate
73 | NSData
74 | NSDecimalNumber
75 | Double
76 | Float
77 | Bool
78 |
79 | These effectvely map to the following :code:`NSAttributeType`
80 | found in :code:`CoreData` which you may also use for your column initialization:
81 |
82 | .. code-block:: swift
83 |
84 | NSAttributeType.StringAttributeType
85 | NSAttributeType.Integer16AttributeType
86 | NSAttributeType.Integer32AttributeType
87 | NSAttributeType.Integer64AttributeType
88 | NSAttributeType.DateAttributeType
89 | NSAttributeType.BinaryDataAttributeType
90 | NSAttributeType.DecimalAttributeType
91 | NSAttributeType.DoubleAttributeType
92 | NSAttributeType.FloatAttributeType
93 | NSAttributeType.BooleanAttributeType
94 | NSAttributeType.UndefinedAttributeType
95 |
96 |
97 | See the initializers in:
98 |
99 | https://github.com/blitzagency/amigo-swift/blob/master/Amigo/Column.swift
100 |
101 |
102 | One additional type exists for Column initialization and that's :code:`Amigo.ForeignKey`
103 |
104 |
105 | ForeignKeys
106 | -------------------
107 |
108 | Amigo allows you to make Foreign Key Relationships. You can do though through
109 | the Managed Object Model or manually.
110 |
111 | In the Managed Object Model, ForeignKeys are represented by a **Relationship**
112 | that has a type of :code:`To One`. That gets translated to the :code:`ORMModel`
113 | mapping as follows:
114 |
115 | .. code-block:: swift
116 |
117 |
118 | import Amigo
119 |
120 | class Dog: AmigoModel{
121 | dynamic var id: NSNumber!
122 | dynamic var label: String!
123 | }
124 |
125 | class Person: AmigoModel{
126 | dynamic var id: NSNumber!
127 | dynamic var label: String!
128 | dynamic var dog: Dog!
129 | }
130 |
131 |
132 | let dog = ORMModel(Dog.self,
133 | Column("id", type: Int.self, primaryKey: true)
134 | Column("label", type: String.self)
135 | )
136 |
137 | // You can use the ORMModel
138 | let person = ORMModel(Person.self,
139 | Column("id", type: Int.self, primaryKey: true)
140 | Column("label", type: String.self)
141 | Column("dog", type: ForeignKey(dog))
142 | )
143 |
144 |
145 | **OR** using the column itself:
146 |
147 | .. code-block:: swift
148 |
149 | // OR you can use the column:
150 | let person = ORMModel(Person.self,
151 | Column("id", type: Int.self, primaryKey: true)
152 | Column("label", type: String.self)
153 | Column("dog", type: ForeignKey(dog.table.c["id"]))
154 | )
155 |
156 |
157 |
158 | One To Many
159 | -------------------
160 |
161 | Using our :code:`Person/Dog` example above, we can also represent a
162 | One To Many relationship.
163 |
164 | In the case of a Managed Object Model, a One To Many is represented by a
165 | **Relationship** that has a type on :code:`To One` on one side and
166 | :code:`To Many` on the other side, aka the inverse relationship.
167 |
168 | In code it would look like this:
169 |
170 |
171 | .. code-block:: swift
172 |
173 | import Amigo
174 |
175 | class Dog: AmigoModel{
176 | dynamic var id: NSNumber!
177 | dynamic var label: String!
178 | }
179 |
180 | class Person: AmigoModel{
181 | dynamic var id: NSNumber!
182 | dynamic var label: String!
183 | dynamic var dog: Dog!
184 | }
185 |
186 | let dog = ORMModel(Dog.self,
187 | Column("id", type: Int.self, primaryKey: true),
188 | Column("label", type: String.self),
189 | OneToMany("people", using: Person.self, on: "dog")
190 | )
191 |
192 | let person = ORMModel(Person.self,
193 | Column("id", type: Int.self, primaryKey: true),
194 | Column("label", type: String.self),
195 | Column("dog", type: ForeignKey(dog))
196 | )
197 |
198 | // specifying 'echo: true' will have amigo print out
199 | // all of the SQL commands it's generating.
200 | let engine = SQLiteEngineFactory(":memory:", echo: true)
201 | amigo = Amigo([dog, person], factory: engine)
202 | amigo.createAll()
203 |
204 |
205 | We can then query the One To Many Relationship this way:
206 |
207 | .. code-block:: swift
208 |
209 | let session = amigo.session
210 |
211 | let d1 = Dog()
212 | d1.label = "Lucy"
213 |
214 | let p1 = People()
215 | p1.label = "Foo"
216 | p1.dog = d1
217 |
218 | let p2 = People()
219 | p2.label = "Bar"
220 | p2.dog = d1
221 |
222 | session.add(d1, p1, p2)
223 |
224 | var results = session
225 | .query(People)
226 | .using(d1)
227 | .relationship("people")
228 | .all()
229 |
230 | Many To Many
231 | -------------------
232 |
--------------------------------------------------------------------------------
/Amigo/Select.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Select.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/7/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func ==(lhs: Join, rhs: Join) -> Bool{
12 | return lhs.hashValue == rhs.hashValue
13 | }
14 |
15 | public func ==(lhs: Select, rhs: Select) -> Bool{
16 | return lhs.hashValue == rhs.hashValue
17 | }
18 |
19 |
20 | public protocol FromClause: Hashable, CustomStringConvertible{
21 |
22 | }
23 |
24 | extension Table {
25 | public func join(table: Table) -> Join{
26 |
27 | let joins = Array(self.columns.values)
28 | .filter{ $0.foreignKey?.relatedColumn == table.primaryKey }
29 | .map{ column -> Join in
30 | let fk = column.foreignKey!
31 | let format = "\(self.label).\(fk.column.label) = \(table.label).\(table.primaryKey!.label)"
32 | return self.join(table, on: format)
33 | }
34 |
35 | return joins[0]
36 | }
37 | }
38 |
39 | extension FromClause{
40 |
41 | public var hashValue: Int{
42 | return description.hashValue
43 | }
44 |
45 | // public func join(table: Table) -> Join{
46 | // guard let current = self as? Table else{
47 | // fatalError("Cannot join from non-table: \(self)")
48 | // }
49 | //
50 | // let format = "\(current.label).\(current.primaryKey!.label) = \(table.label).\(table.primaryKey!.label)"
51 | // return self.join(table, on: format)
52 | // }
53 |
54 | public func join(table: Table, on: String, _ args: AnyObject...) -> Join{
55 | return self.join(table, on: on, args: args)
56 | }
57 |
58 | public func join(table: Table, on: String, args: [AnyObject]?) -> Join{
59 | guard let current = self as? Table else{
60 | fatalError("Cannot join from non-table: \(self)")
61 | }
62 |
63 | return Join(current, right: table, on: on)
64 | }
65 |
66 | public func select() -> Select{
67 | if let table = self as? Table{
68 | return Select(table)
69 | }
70 |
71 | fatalError("Cannot select from non-table: \(self)")
72 | }
73 |
74 | public func insert() -> Insert{
75 | if let table = self as? Table{
76 | return Insert(table)
77 | }
78 |
79 | fatalError("Cannot insert from non-table: \(self)")
80 | }
81 |
82 | public func insert(upsert upsert: Bool) -> Insert{
83 | if let table = self as? Table{
84 | return Insert(table, upsert: upsert)
85 | }
86 |
87 | fatalError("Cannot insert from non-table: \(self)")
88 | }
89 |
90 |
91 | public func update() -> Update{
92 | if let table = self as? Table{
93 | return Update(table)
94 | }
95 |
96 | fatalError("Cannot update from non-table: \(self)")
97 | }
98 |
99 | public func delete() -> Delete{
100 | if let table = self as? Table{
101 | return Delete(table)
102 | }
103 |
104 | fatalError("Cannot delete from non-table: \(self)")
105 | }
106 | }
107 |
108 |
109 | public class Join: FromClause{
110 | public let left: Table
111 | public let right: Table
112 | public let on: String
113 |
114 | public init(_ left: Table, right: Table, on: String){
115 | self.left = left
116 | self.right = right
117 | self.on = on
118 | }
119 |
120 | public var description: String{
121 | return ""
122 | }
123 | }
124 |
125 |
126 | public class Select: FromClause, Filterable{
127 |
128 | public let columns: [Column]
129 | // swift doesn't have an ordered set in it's stdlib so
130 | // we fake it
131 | public var from = [AnyObject]()
132 | public var predicate: String?
133 | public var predicateParams: [AnyObject]?
134 |
135 | var hasFrom = [String: AnyObject]()
136 | var _limit: Int?
137 | var _offset: Int?
138 | var _orderBy = [OrderBy]()
139 |
140 | public convenience init(_ tables: Table...){
141 | self.init(tables)
142 | }
143 |
144 | public convenience init(_ tables:[Table]){
145 | let columns = tables.map{$0.sortedColumns}.flatMap{$0}
146 | self.init(columns)
147 |
148 | tables.forEach(appendFrom)
149 | }
150 |
151 | public convenience init(_ columns: Column...){
152 | self.init(columns)
153 | }
154 |
155 | public init(_ columns: [Column]){
156 | self.columns = columns
157 | }
158 |
159 | public func appendFrom(obj: T){
160 | let desc = obj.description
161 | // this isn't a great algorithim for the speed of things
162 | // it would be better if we had an ordered set for this.
163 |
164 | if let join = obj as? Join{
165 | if let table = hasFrom[join.right.description] as? Table{
166 | for (index, each) in from.enumerate(){
167 | if let candidate = each as? Table{
168 | if candidate == table {
169 | from.removeAtIndex(index)
170 | break
171 | }
172 | }
173 | }
174 | }
175 | }
176 |
177 |
178 | if let _ = hasFrom[desc]{
179 | return
180 | }
181 |
182 | let target = obj as! AnyObject
183 |
184 | hasFrom[desc] = target
185 | from.append(target)
186 | }
187 |
188 | public func selectFrom(from: T...) -> Select{
189 | from.forEach(appendFrom)
190 | return self
191 | }
192 |
193 | // public func selectFrom(from: [T]...) -> Select{
194 | // from.map(selectFrom)
195 | // return self
196 | // }
197 |
198 | public func selectFrom(from: [T]) -> Select{
199 | from.forEach(appendFrom)
200 | return self
201 | }
202 |
203 | public func limit(value: Int) -> Select{
204 | _limit = value
205 | return self
206 | }
207 |
208 | public func offset(value: Int) -> Select{
209 | _offset = value
210 | return self
211 | }
212 |
213 | public func orderBy(value: OrderBy...) -> Select{
214 | return orderBy(value)
215 | }
216 |
217 | public func orderBy(value: [OrderBy]) -> Select{
218 | _orderBy.appendContentsOf(value)
219 | return self
220 | }
221 |
222 | public var description: String{
223 | let names = columns.map{"\($0.table!.label).\($0.label)"}
224 | let out = names.joinWithSeparator(" ")
225 | return ""
226 | }
227 | }
--------------------------------------------------------------------------------
/docs/querying/index.rst:
--------------------------------------------------------------------------------
1 | Querying
2 | ===================================
3 |
4 | Querying with :code:`Amigo` is similar to querying with Django or
5 | SQLAlchemy. Lets run though a few examples. In each of the following
6 | examples we will assume we have already done our model mapping
7 | and we have an :code:`amigo` instance available to us. For
8 | more information on model mapping see: :doc:`/models/index`
9 |
10 |
11 | Get an object by id
12 | ---------------------
13 |
14 | :code:`get` returns an optional model as it may fail if the id
15 | is not present.
16 |
17 | .. code-block:: swift
18 |
19 | let session = amigo.session
20 | let maybeDog: Dog? = session.query(Dog).get(1)
21 |
22 |
23 | Get all objects
24 | ----------------------------------
25 |
26 | :code:`all`
27 |
28 | .. code-block:: swift
29 |
30 | let session = amigo.session
31 | let dogs: [Dog] = session.query(Dog).all()
32 |
33 |
34 | Order objects
35 | ----------------------------------
36 |
37 | :code:`orderBy`
38 |
39 | .. code-block:: swift
40 |
41 | let session = amigo.session
42 | let dogs: [Dog] = session
43 | .query(Dog)
44 | .orderBy("label", ascending: false) // by default ascending is true
45 | .all()
46 |
47 |
48 | Filter objects
49 | ----------------------------------
50 |
51 | :code:`filter`
52 |
53 | .. code-block:: swift
54 |
55 | let session = amigo.session
56 | let dogs: [Dog] = session
57 | .query(Dog)
58 | .filter("id > 3")
59 | .all()
60 |
61 | .. note ::
62 |
63 | Filter strings are converted into a :code:`NSPredicate` behind the
64 | scenes. When using the :code:`SQLiteEngine`, the constant params are
65 | extracted and replaced with `?` in generated query. The params are
66 | then passed to FMDB for escaping/replacement.
67 |
68 |
69 | Limit objects
70 | ----------------------------------
71 |
72 | :code:`limit`
73 |
74 | .. code-block:: swift
75 |
76 | let session = amigo.session
77 | let dogs: [Dog] = session
78 | .query(Dog)
79 | .limit(10)
80 | .all()
81 |
82 |
83 |
84 | Offset objects
85 | ----------------------------------
86 |
87 | :code:`offset`
88 |
89 | .. code-block:: swift
90 |
91 | let session = amigo.session
92 | let dogs: [Dog] = session
93 | .query(Dog)
94 | .limit(10)
95 | .offset(5)
96 | .all()
97 |
98 |
99 | Full foreign key in one query (aka JOIN)
100 | ----------------------------------------
101 |
102 | :code:`selectRelated`
103 |
104 | See :ref:`foreign-key` for more.
105 |
106 | .. code-block:: swift
107 |
108 | let dog = ORMModel(Dog.self,
109 | Column("id", type: Int.self, primaryKey: true)
110 | Column("label", type: String.self)
111 | )
112 |
113 | // You can use the ORMModel
114 | let person = ORMModel(Person.self,
115 | Column("id", type: Int.self, primaryKey: true)
116 | Column("label", type: String.self)
117 | Column("dog", type: ForeignKey(dog))
118 | )
119 |
120 | // specifying 'echo: true' will have amigo print out
121 | // all of the SQL commands it's generating.
122 | let engine = SQLiteEngineFactory(":memory:", echo: true)
123 | amigo = Amigo([dog, person], factory: engine)
124 | amigo.createAll()
125 |
126 | let session = amigo.session
127 |
128 | let d1 = Dog()
129 | d1.label = "Lucy"
130 |
131 | let p1 = Person()
132 | p1.label = "Foo"
133 | p1.dog = d1
134 |
135 | session.add(d1, p1)
136 |
137 | let result = session
138 | .query(Person)
139 | .selectRelated("dog")
140 | .all()
141 |
142 |
143 | Filter and Order By related fields
144 | -----------------------------------
145 |
146 | :code:`filter`
147 | :code:`orderBy`
148 |
149 | .. code-block:: swift
150 |
151 | let dog = ORMModel(Dog.self,
152 | Column("id", type: Int.self, primaryKey: true)
153 | Column("label", type: String.self)
154 | )
155 |
156 | // You can use the ORMModel
157 | let person = ORMModel(Person.self,
158 | Column("id", type: Int.self, primaryKey: true)
159 | Column("label", type: String.self)
160 | Column("dog", type: ForeignKey(dog))
161 | )
162 |
163 | // specifying 'echo: true' will have amigo print out
164 | // all of the SQL commands it's generating.
165 | let engine = SQLiteEngineFactory(":memory:", echo: true)
166 | amigo = Amigo([dog, person], factory: engine)
167 | amigo.createAll()
168 |
169 | let session = amigo.session
170 |
171 | let d1 = Dog()
172 | d1.label = "Lucy"
173 |
174 | let p1 = Person()
175 | p1.label = "Foo"
176 | p1.dog = d1
177 |
178 | session.add(d1, p1)
179 |
180 | let result = session
181 | .query(Person)
182 | .selectRelated("dog")
183 | .filter("id > 1 AND dog.id > 1") // note the dot notation
184 | .orderBy("dog.id", ascending: false) // note the dot notation
185 | .all()
186 |
187 |
188 | One-To-Many Query
189 | -----------------------------------
190 |
191 | :code:`relationship`
192 |
193 | See :ref:`one-to-many` for the full example.
194 |
195 | .. code-block:: swift
196 |
197 | let session = amigo.session
198 | var results = session
199 | .query(People) // We want the People objects
200 | .using(d1) // by using the d1 (Dog) object
201 | .relationship("people") // and following the d1 model's "people" relationship
202 | .all()
203 |
204 |
205 |
206 | Many-To-Many Query
207 | -----------------------------------
208 |
209 | :code:`relationship`
210 |
211 | See :ref:`many-to-many` for the full example.
212 |
213 | .. code-block:: swift
214 |
215 | let session = amigo.session
216 | var results = session
217 | .query(Child) // We want the Child objects
218 | .using(p1) // by using the p1 (Parent) object
219 | .relationship("children") // and following the d1 model's "children" relationship
220 | .all()
221 |
222 |
223 | Many-To-Many With Through Models Query
224 | ---------------------------------------
225 |
226 | :code:`relationship`
227 |
228 | See :ref:`many-to-many-through-models` for the full example.
229 |
230 | .. code-block:: swift
231 |
232 | let session = amigo.session
233 |
234 | var results = session
235 | .query(WorkoutMeta) // We want the WorkoutMeta objects
236 | .using(w1) // by using the w1 (Workout) object
237 | .relationship("exercises") // and following the w1 model's "exercises" relationship
238 | .orderBy("position", ascending: true) // order the results by WorkoutMeta.position ascending
239 | .all()
240 |
241 |
--------------------------------------------------------------------------------
/AmigoTests/AmigoQuerySetTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AmigoQuerySetTests.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 7/24/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 | import Amigo
12 |
13 | class AmigoQuerySetTests: AmigoTestBase {
14 |
15 |
16 | func testMultipleForeignKeys(){
17 | let session = amigo.session
18 |
19 | let d1 = Dog()
20 | d1.label = "Lucy"
21 |
22 | let c1 = Cat()
23 | c1.label = "Ollie"
24 |
25 | let p1 = People()
26 | p1.label = "Foo"
27 | p1.dog = d1
28 | p1.cat = c1
29 |
30 | session.add(p1)
31 |
32 | let people = session
33 | .query(People)
34 | .selectRelated("dog", "cat")
35 | .all()
36 |
37 | XCTAssertEqual(people.count, 1)
38 | XCTAssertNotNil(people[0].dog)
39 | XCTAssertNotNil(people[0].cat)
40 | XCTAssertEqual(people[0].dog.label, "Lucy")
41 | XCTAssertEqual(people[0].cat.label, "Ollie")
42 | }
43 |
44 | func testOrderByDifferentTable(){
45 | let session = amigo.session
46 |
47 | let d1 = Dog()
48 | d1.label = "Lucy"
49 |
50 | let d2 = Dog()
51 | d2.label = "Ollie"
52 |
53 | let p1 = People()
54 | p1.label = "Foo"
55 | p1.dog = d1
56 |
57 | let p2 = People()
58 | p2.label = "Bar"
59 | p2.dog = d2
60 |
61 | session.add(p1)
62 | session.add(p2)
63 |
64 | let people = session
65 | .query(People)
66 | .selectRelated("dog")
67 | .orderBy("dog.label", ascending: false)
68 | .all()
69 |
70 | XCTAssertEqual(people.count, 2)
71 | XCTAssertEqual(people[0].id, 2)
72 | XCTAssertEqual(people[0].dog.label, "Ollie")
73 | }
74 |
75 | func testFilterBy(){
76 | let session = amigo.session
77 |
78 | let d1 = Dog()
79 | d1.label = "Lucy"
80 |
81 | let d2 = Dog()
82 | d2.label = "Ollie"
83 |
84 | let p1 = People()
85 | p1.label = "Foo"
86 | p1.dog = d1
87 |
88 | let p2 = People()
89 | p2.label = "Bar"
90 | p2.dog = d2
91 |
92 | session.add(p1)
93 | session.add(p2)
94 |
95 | let people = session
96 | .query(People)
97 | .filter("label = 'Foo'")
98 | .all()
99 |
100 | XCTAssertEqual(people.count, 1)
101 | XCTAssertEqual(people[0].id, 1)
102 | }
103 |
104 | func testFilterByDifferentTable(){
105 | let session = amigo.session
106 |
107 | let d1 = Dog()
108 | d1.label = "Lucy"
109 |
110 | let d2 = Dog()
111 | d2.label = "Ollie"
112 |
113 | let p1 = People()
114 | p1.label = "Foo"
115 | p1.dog = d1
116 |
117 | let p2 = People()
118 | p2.label = "Bar"
119 | p2.dog = d2
120 |
121 | session.add(p1)
122 | session.add(p2)
123 |
124 | let people = session
125 | .query(People)
126 | .selectRelated("dog")
127 | .orderBy("dog.label", ascending: false)
128 | .filter("dog.label = \"Lucy\"")
129 | .all()
130 |
131 | XCTAssertEqual(people.count, 1)
132 | XCTAssertEqual(people[0].id, 1)
133 | XCTAssertEqual(people[0].dog.label, "Lucy")
134 | }
135 |
136 | func testOrderBySameTable(){
137 | let session = amigo.session
138 |
139 | let d1 = Dog()
140 | d1.label = "Lucy"
141 |
142 | let d2 = Dog()
143 | d2.label = "Ollie"
144 |
145 | session.add(d1)
146 | session.add(d2)
147 |
148 | var dogs = session
149 | .query(Dog)
150 | .orderBy("label", ascending: false)
151 | .all()
152 |
153 | XCTAssertEqual(dogs.count, 2)
154 | XCTAssertEqual(dogs[0].id, 2)
155 | XCTAssertEqual(dogs[0].label, "Ollie")
156 |
157 | dogs = session
158 | .query(Dog)
159 | .orderBy("label")
160 | .all()
161 |
162 | XCTAssertEqual(dogs.count, 2)
163 | XCTAssertEqual(dogs[0].id, 1)
164 | XCTAssertEqual(dogs[0].label, "Lucy")
165 | }
166 |
167 | func testForeignKeyAutoSave(){
168 | let session = amigo.session
169 |
170 | let d1 = Dog()
171 | d1.label = "Lucy"
172 |
173 | let p1 = People()
174 | p1.label = "Ollie Cat"
175 | p1.dog = d1
176 |
177 | var candidate = session.query(Dog).get(1)
178 | XCTAssertNil(candidate)
179 |
180 | session.add(p1)
181 |
182 | candidate = session.query(Dog).get(1)
183 | XCTAssertNotNil(candidate)
184 | }
185 |
186 | func testSelectRelated(){
187 | let session = amigo.session
188 |
189 | let d1 = Dog()
190 | d1.label = "Lucy"
191 |
192 | let p1 = People()
193 | p1.label = "Ollie Cat"
194 | p1.dog = d1
195 |
196 | session.add(p1)
197 |
198 | var person = session.query(People).get(1)!
199 | XCTAssertNil(person.dog)
200 |
201 | person = session
202 | .query(People)
203 | .selectRelated("dog")
204 | .get(1)!
205 |
206 | XCTAssertNotNil(person.dog)
207 | XCTAssertEqual(person.dog.label, "Lucy")
208 | }
209 |
210 | func testLimit(){
211 | let session = amigo.session
212 |
213 | let a1 = Author()
214 | a1.firstName = "Lucy"
215 | a1.lastName = "Dog"
216 |
217 | let a2 = Author()
218 | a2.firstName = "Ollie"
219 | a2.lastName = "Cat"
220 |
221 | session.add(a1)
222 | session.add(a2)
223 |
224 | let authors = session
225 | .query(Author)
226 | .limit(1)
227 | .all()
228 |
229 | XCTAssertEqual(authors.count, 1)
230 | XCTAssertEqual(authors[0].firstName, "Lucy")
231 | XCTAssertEqual(authors[0].lastName, "Dog")
232 | }
233 |
234 | func testOffset(){
235 | let session = amigo.session
236 |
237 | let a1 = Author()
238 | a1.firstName = "Lucy"
239 | a1.lastName = "Dog"
240 |
241 | let a2 = Author()
242 | a2.firstName = "Ollie"
243 | a2.lastName = "Cat"
244 |
245 | session.add(a1)
246 | session.add(a2)
247 |
248 | let authors = session
249 | .query(Author)
250 | .limit(1)
251 | .offset(1)
252 | .all()
253 |
254 | XCTAssertEqual(authors.count, 1)
255 | XCTAssertEqual(authors[0].firstName, "Ollie")
256 | XCTAssertEqual(authors[0].lastName, "Cat")
257 | }
258 |
259 | func testOneToMany(){
260 |
261 | let session = amigo.session
262 |
263 | let a1 = Author()
264 | a1.firstName = "Lucy"
265 | a1.lastName = "Dog"
266 |
267 | let a2 = Author()
268 | a2.firstName = "Ollie"
269 | a2.lastName = "Cat"
270 |
271 | let p1 = Post()
272 | p1.title = "The Story of Barking"
273 | p1.author = a1
274 |
275 | let p2 = Post()
276 | p2.title = "10 Things You Should Know When Chasing Squirrels"
277 | p2.author = a1
278 |
279 | let p3 = Post()
280 | p3.title = "The Story of Being a Cat"
281 | p3.author = a2
282 |
283 | session.add(a1)
284 | session.add(a2)
285 | session.add(p1)
286 | session.add(p2)
287 | session.add(p3)
288 |
289 | let posts = session.query(Post)
290 | .using(a1)
291 | .relationship("posts")
292 | .all()
293 |
294 | XCTAssertEqual(posts.count, 2)
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/AmigoTests/App.xcdatamodeld/App.xcdatamodel/contents:
--------------------------------------------------------------------------------
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 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/Amigo/Amigo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Amigo.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | public class Amigo: AmigoConfigured{
14 |
15 | public let config: AmigoConfiguration
16 | public let engineFactory: EngineFactory
17 |
18 | public func query(value: T.Type) -> QuerySet{
19 | let model = typeIndex[value.description()]!
20 | return QuerySet(model: model, config: threadConfig())
21 | }
22 |
23 | public func async(queue queue: dispatch_queue_t? = nil, action: () -> T) -> AmigoSessionAsyncAction{
24 |
25 | if let queue = queue{
26 | return AmigoSessionAsyncAction(action: action, queue: queue)
27 | }
28 |
29 | return AmigoSessionAsyncAction(action: action)
30 | }
31 |
32 | public func async(queue queue: dispatch_queue_t? = nil, action: () -> ()){
33 | if let queue = queue{
34 | AmigoSessionAsyncAction(action: action, queue: queue).run()
35 | return
36 | }
37 |
38 | AmigoSessionAsyncAction(action: action).run()
39 | }
40 |
41 | public var session: AmigoSession{
42 | // there is currently an issue here with multiple threads.
43 | // a session could be grabbed from each thread, but in the current
44 | // model they are effectively using the same connection.
45 | // there will be many unintended consequences.
46 | //
47 | // it may be best here to use a new connection per session
48 | // we would need to ensure sqlite is compiled in multi-threaded mode:
49 | // https://www.sqlite.org/threadsafe.html
50 | //
51 | // for now lets just get things working so we are ignoring it.
52 | // in the case of FMDB, per the docs it's always been safe to make
53 | // a FMDatabase object per thread.
54 |
55 |
56 | let session = AmigoSession(config: threadConfig())
57 | session.begin()
58 | return session
59 | }
60 |
61 | public convenience init(_ mom: NSManagedObjectModel, factory: EngineFactory, mapperType: AmigoEntityDescriptionMapper.Type = EntityDescriptionMapper.self){
62 | let mapper = mapperType.init()
63 | let models = mom.sortedEntities().map(mapper.map)
64 | self.init(models, factory: factory, mapper: mapper)
65 | }
66 |
67 | public convenience init(_ models: [ORMModel], factory: EngineFactory, mapperType: Mapper.Type = DefaultMapper.self){
68 | let mapper = mapperType.init()
69 | self.init(models, factory: factory, mapper: mapper)
70 | }
71 |
72 | public init(_ models: [ORMModel], factory: EngineFactory, mapper: Mapper){
73 | var tableIndex = [String:ORMModel]()
74 | var typeIndex = [String:ORMModel]()
75 |
76 | models.forEach{
77 | tableIndex[$0.table.label] = $0
78 | typeIndex[$0.type] = $0
79 | }
80 |
81 | engineFactory = factory
82 |
83 | self.config = AmigoConfiguration(
84 | engine: factory.connect(),
85 | mapper: mapper,
86 | tableIndex: tableIndex,
87 | typeIndex: typeIndex
88 | )
89 |
90 | initializeOneToMany(models)
91 | initializeManyToMany(models)
92 |
93 | }
94 |
95 | func initializeOneToMany(models:[ORMModel]){
96 | let relationships = models
97 | .map{Array($0.relationships.values)}
98 | .flatMap{$0}
99 |
100 | let o2m = relationships.filter{$0 is OneToMany}.map{$0 as! OneToMany}
101 |
102 | for each in o2m{
103 | if each.column == nil{
104 | let originModel = config.tableIndex[each.originTable]!
105 | let destinationModel = config.tableIndex[each.table]!
106 |
107 | for (key, value) in destinationModel.foreignKeys{
108 | if value.foreignKey!.relatedTable == originModel.table{
109 | each.column = key
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | func initializeManyToMany(models:[ORMModel]){
117 | // find all the ManyToMany Relationships so we can inject tables.
118 | let relationships = models
119 | .map{Array($0.relationships.values)}
120 | .flatMap{$0}
121 |
122 | let m2m = relationships.filter{$0 is ManyToMany}.map{$0 as! ManyToMany}
123 | var m2mHash = [ManyToMany: [ManyToMany]]()
124 | var m2mThroughModels = [ManyToMany]()
125 |
126 | m2m.forEach{ (value: ManyToMany) -> Void in
127 | value.left = config.tableIndex[value.tables[0]]!
128 | value.right = config.tableIndex[value.tables[1]]!
129 |
130 | if var container = m2mHash[value]{
131 | container.append(value)
132 | m2mHash[value] = container
133 | } else {
134 | let container = [value]
135 | m2mHash[value] = container
136 | }
137 |
138 | if let throughModel = value.throughModel{
139 | let model = config.typeIndex[throughModel]!
140 |
141 | model.throughModelRelationship = value
142 | value.through = model
143 |
144 | Array(model.foreignKeys.values).forEach{ (c: Column) -> Void in
145 | if c.foreignKey!.relatedColumn == value.left.primaryKey || c.foreignKey!.relatedColumn == value.right.primaryKey{
146 | c.optional = false
147 | }
148 | }
149 |
150 | m2mThroughModels.append(value)
151 | }
152 | }
153 |
154 | // ensure the throughModel is registered on both
155 | // sides of the relationship
156 | m2mThroughModels.forEach{ (value: ManyToMany) -> Void in
157 | m2mHash[value]?.forEach{ (each: ManyToMany) -> Void in
158 | each.throughModel = value.throughModel
159 | each.through = value.through
160 | }
161 | }
162 |
163 | // use the set to omit duplicates
164 | Set(m2m).forEach{ (value: ManyToMany) -> Void in
165 | let left = "\(value.left.label)_\(value.left.primaryKey!.label)"
166 | let right = "\(value.right.label)_\(value.right.primaryKey!.label)"
167 |
168 | var columns: [SchemaItem] = [
169 | Column("id", type: Int.self, primaryKey: true),
170 | Column(left, type: ForeignKey(value.left.table), indexed: true, optional: false),
171 | Column(right, type: ForeignKey(value.right.table), indexed: true, optional: false)
172 | ]
173 |
174 | if let model = value.through{
175 | let label = "\(model.label)_\(model.primaryKey!.label)"
176 | columns.append(Column(label, type: ForeignKey(model.table), indexed: true, optional: false))
177 | }
178 |
179 | let table = Table(value.tableName, metadata: ORMModel.metadata, items: columns)
180 |
181 | m2mHash[value]?.forEach{ (value: ManyToMany) -> Void in
182 | value.associationTable = table
183 | }
184 | }
185 | }
186 |
187 | func threadConfig() -> AmigoConfiguration{
188 | let obj = AmigoConfiguration(
189 | engine: engineFactory.connect(),
190 | mapper: config.mapper,
191 | tableIndex: config.tableIndex,
192 | typeIndex: config.typeIndex
193 | )
194 |
195 | return obj
196 | }
197 |
198 | public func execute(sql: String, params: [AnyObject]! = nil){
199 | config.engine.execute(sql, params: params)
200 | }
201 |
202 | public func execute(sql: String, params: [AnyObject]! = nil, mapper: Input -> Output) -> Output {
203 | return config.engine.execute(sql, params: params, mapper: mapper)
204 | }
205 |
206 | public func createAll(){
207 | ORMModel.metadata.createAll(config.engine)
208 | }
209 |
210 |
211 | }
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " applehelp to make an Apple Help Book"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 |
51 | clean:
52 | rm -rf $(BUILDDIR)/*
53 |
54 | html:
55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
56 | @echo
57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
58 |
59 | dirhtml:
60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
61 | @echo
62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
63 |
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | pickle:
70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 | @echo
72 | @echo "Build finished; now you can process the pickle files."
73 |
74 | json:
75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
76 | @echo
77 | @echo "Build finished; now you can process the JSON files."
78 |
79 | htmlhelp:
80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
81 | @echo
82 | @echo "Build finished; now you can run HTML Help Workshop with the" \
83 | ".hhp project file in $(BUILDDIR)/htmlhelp."
84 |
85 | qthelp:
86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
87 | @echo
88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Amigo.qhcp"
91 | @echo "To view the help file:"
92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Amigo.qhc"
93 |
94 | applehelp:
95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
96 | @echo
97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
98 | @echo "N.B. You won't be able to view it unless you put it in" \
99 | "~/Library/Documentation/Help or install it in your application" \
100 | "bundle."
101 |
102 | devhelp:
103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
104 | @echo
105 | @echo "Build finished."
106 | @echo "To view the help file:"
107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Amigo"
108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Amigo"
109 | @echo "# devhelp"
110 |
111 | epub:
112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
113 | @echo
114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
115 |
116 | latex:
117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
118 | @echo
119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
121 | "(use \`make latexpdf' here to do that automatically)."
122 |
123 | latexpdf:
124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
125 | @echo "Running LaTeX files through pdflatex..."
126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
128 |
129 | latexpdfja:
130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 | @echo "Running LaTeX files through platex and dvipdfmx..."
132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
134 |
135 | text:
136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
137 | @echo
138 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
139 |
140 | man:
141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
142 | @echo
143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
144 |
145 | texinfo:
146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
147 | @echo
148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
149 | @echo "Run \`make' in that directory to run these through makeinfo" \
150 | "(use \`make info' here to do that automatically)."
151 |
152 | info:
153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
154 | @echo "Running Texinfo files through makeinfo..."
155 | make -C $(BUILDDIR)/texinfo info
156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
157 |
158 | gettext:
159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
160 | @echo
161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
162 |
163 | changes:
164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
165 | @echo
166 | @echo "The overview file is in $(BUILDDIR)/changes."
167 |
168 | linkcheck:
169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
170 | @echo
171 | @echo "Link check complete; look for any errors in the above output " \
172 | "or in $(BUILDDIR)/linkcheck/output.txt."
173 |
174 | doctest:
175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
176 | @echo "Testing of doctests in the sources finished, look at the " \
177 | "results in $(BUILDDIR)/doctest/output.txt."
178 |
179 | coverage:
180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
181 | @echo "Testing of coverage in the sources finished, look at the " \
182 | "results in $(BUILDDIR)/coverage/python.txt."
183 |
184 | xml:
185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
186 | @echo
187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
188 |
189 | pseudoxml:
190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
191 | @echo
192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
193 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 2> nul
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Amigo.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Amigo.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/Amigo/QuerySet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuerySet.swift
3 | // Amigo
4 | //
5 | // Created by Adam Venturella on 6/29/15.
6 | // Copyright © 2015 BLITZ. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | public class QuerySet: AmigoConfigured{
13 |
14 | public let config: AmigoConfiguration
15 | public let model: ORMModel
16 |
17 | public var maybePredicate: NSPredicate?
18 |
19 | var _related = [String]()
20 | var _values = [String]()
21 | var _using : AmigoModel?
22 | var _relationship: String?
23 | var _limit: Int?
24 | var _offset: Int?
25 | var _orderBy = [OrderBy]()
26 |
27 |
28 | init(model: ORMModel, config: AmigoConfiguration){
29 | self.config = config
30 | self.model = model
31 | }
32 |
33 | public func selectRelated(values: String...) -> QuerySet{
34 | _related = values
35 | return self
36 | }
37 |
38 | public func relationship(value: String) -> QuerySet{
39 | _relationship = value
40 | return self
41 | }
42 |
43 | public func values(values: String...) -> QuerySet{
44 | _values = values
45 | return self
46 | }
47 |
48 | public func limit(value:Int) -> QuerySet{
49 | _limit = value
50 | return self
51 | }
52 |
53 | public func offset(value:Int) -> QuerySet{
54 | _offset = value
55 | return self
56 | }
57 |
58 | public func orderBy(key: String, ascending: Bool = true) -> QuerySet{
59 | let order = ascending ? Asc(key) : Desc(key) as OrderBy
60 | _orderBy.append(order)
61 | return self
62 | }
63 |
64 | public func filter(format: String) -> QuerySet{
65 | maybePredicate = NSPredicate(format: format)
66 | return self
67 | }
68 |
69 | public func using(model: U) -> QuerySet{
70 | _using = model
71 |
72 | return self
73 | }
74 |
75 | public func get(primaryKey: AnyObject) -> T?{
76 | self.filter("\(model.primaryKey.label) = \"\(primaryKey)\"")
77 | let results = self.all()
78 | return results.first
79 | }
80 |
81 | public func all() -> [T]{
82 | return engine.execute(self)
83 | }
84 |
85 | public func count() -> Int{
86 | // var error: NSError?
87 | // let request = fetchRequest
88 | // request.returnsObjectsAsFaults = true
89 | //
90 | // let count = context.countForFetchRequest(fetchRequest, error: &error)
91 | return 0
92 | }
93 |
94 | public func compile() -> Select {
95 | // if we are doing a selectRelated, we need to set all
96 | // of the columns 1st
97 | var joins = [Join]()
98 | var columns : [Column]
99 | var select: Select
100 | let explicitColumns: Bool
101 | let fromTable: Table
102 |
103 | if _values.count == 0 {
104 | explicitColumns = false
105 | columns = model.columns
106 | } else {
107 | explicitColumns = true
108 | columns = getExplicitColumns()
109 | }
110 |
111 | for each in _related{
112 | if let fkColumn = model.foreignKeys[each]{
113 | let relatedTable = fkColumn.foreignKey!.relatedColumn.table!
114 | let join = model.table.join(relatedTable)
115 | let joinColumns = tableIndex[relatedTable.label]!.columns
116 |
117 | if explicitColumns == false {
118 | columns = columns + joinColumns
119 | }
120 |
121 | joins.append(join)
122 | }
123 | }
124 |
125 | // for a many to many we change the table here to the
126 | // pivot table. and append two joins (select related)
127 | // to the selectFrom
128 |
129 | if let using = _using,
130 | let key = _relationship,
131 | let relatedModel = config.typeIndex[using.dynamicType.description()],
132 | let relationship = relatedModel.relationships[key] as? ManyToMany{
133 | /*
134 | SELECT w.label, m.workout_id, m.id, m.type, m.count, e.label
135 | FROM workout_exercise AS we
136 | LEFT JOIN meta AS m ON m.id = we.meta_id
137 | LEFT JOIN exercise AS e ON e.id = we.exercise_id
138 | LEFT JOIN workout AS w ON w.id = we.workout_id
139 | ORDER BY we.workout_id
140 | */
141 |
142 | select = Select(columns)
143 | .selectFrom(relationship.associationTable)
144 |
145 | if let through = relationship.through{
146 | select.selectFrom(relationship.associationTable.join(through.table))
147 | fromTable = through.table
148 |
149 | } else {
150 | let joinTarget = relationship.left == model ?
151 | relationship.associationTable.join(relationship.left.table) :
152 | relationship.associationTable.join(relationship.right.table)
153 |
154 | select.selectFrom(joinTarget)
155 | fromTable = relationship.associationTable
156 | }
157 |
158 | } else {
159 | fromTable = model.table
160 | select = Select(columns).selectFrom(model.table)
161 | }
162 |
163 |
164 | if let (filter, params) = compileFilter(fromTable){
165 | select.filter(filter, params: params)
166 | }
167 |
168 | select.orderBy(_orderBy.map{ (value: OrderBy) -> OrderBy in
169 | let parts = value.keyPath.unicodeScalars.split{$0 == "."}.map(String.init)
170 |
171 | if parts.count == 1{
172 | let name = model.table.c[parts[0]]!.qualifiedLabel!
173 | return value.dynamicType.init(name)
174 | } else if parts.count == 2{
175 | let column = model.table.c[parts[0] + "_id"]!
176 | let table = column.foreignKey!.relatedColumn.table!
177 | let name = table.c[parts[1]]!.qualifiedLabel!
178 |
179 | return value.dynamicType.init(name)
180 | }
181 |
182 | return value.dynamicType.init(value.keyPath)
183 | })
184 |
185 | if let limit = _limit{
186 | select.limit(limit)
187 | }
188 |
189 | if let offset = _offset{
190 | select.offset(offset)
191 | }
192 |
193 | if joins.count > 0 {
194 | select.selectFrom(joins)
195 | }
196 |
197 | return select
198 | }
199 |
200 | func compileFilter(fromTable: Table) -> (String, [AnyObject])?{
201 | var predicate: NSPredicate? = maybePredicate
202 |
203 |
204 | if let using = _using, let key = _relationship, let relatedModel = config.typeIndex[using.dynamicType.description()]{
205 | // there is gonna be a problem here. If 2 libs use "author" the
206 | // qualified names will be fine, but the dotted names
207 | // could have naming conflicts that can't be resolved
208 | // without a fully qualified name.
209 | if let relationship = relatedModel.relationships[key] as? OneToMany {
210 | let id = using.valueForKey(relatedModel.primaryKey!.label)!
211 | let relatedTable = fromTable.model!.foreignKeys[relationship.column]!.foreignKey!.relatedTable
212 |
213 | let suffix = relatedTable.primaryKey!.label
214 | let p = "\(relationship.column)_\(suffix) = \"\(id)\""
215 |
216 | let relationshipPredicate = NSPredicate(format: p)
217 |
218 | if let filterPredicate = predicate{
219 | predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filterPredicate, relationshipPredicate])
220 | } else {
221 | predicate = relationshipPredicate
222 | }
223 | }
224 |
225 | if let relationship = relatedModel.relationships[key] as? ManyToMany {
226 | let obj = relationship.left == relatedModel ? relationship.left : relationship.right
227 | let column = "\(obj.label)_\(obj.primaryKey!.label)"
228 | let id = using.valueForKey(obj.primaryKey!.label)!
229 | let p = "\(column) = \"\(id)\""
230 |
231 | let relationshipPredicate = NSPredicate(format: p)
232 |
233 | if let filterPredicate = predicate{
234 | predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filterPredicate, relationshipPredicate])
235 | } else {
236 | predicate = relationshipPredicate
237 | }
238 | }
239 | }
240 |
241 | if let predicate = predicate{
242 | let (filter, params) = engine.compiler.compile(predicate, table: fromTable, models: config.tableIndex)
243 | return(filter, params)
244 | } else {
245 | return nil
246 | }
247 |
248 | }
249 |
250 | func getExplicitColumns() -> [Column]{
251 |
252 | let cols = _values.map{ value -> Column in
253 | let parts = value.unicodeScalars.split{ $0 == "."}.map(String.init)
254 |
255 | if parts.count == 1{
256 | return model.table.columns[parts[0]]!
257 | } else {
258 | // this is a FK Column. We append "_id" as the ORM hides
259 | // that idea from the user.
260 | let column = model.table.columns[parts[0] + "_id"]!
261 | let table = column.foreignKey!.relatedColumn.table!
262 | return table.columns[parts[1]]!
263 | }
264 | }
265 |
266 | return cols
267 | }
268 | }
--------------------------------------------------------------------------------