Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
helje5 committed Nov 20, 2024
2 parents 520cdbd + d6886e0 commit 44644a8
Show file tree
Hide file tree
Showing 27 changed files with 718 additions and 201 deletions.
38 changes: 21 additions & 17 deletions Sources/ZeeQL/Access/AccessDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,37 @@
// ZeeQL
//
// Created by Helge Hess on 24/02/17.
// Copyright © 2017-2020 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
* This class has a set of operations targetted at SQL based applications. It
* has three major subclasses with specific characteristics:
*
* - `DatabaseDataSource`
* - `ActiveDataSource`
* - `AdaptorDataSource`
* - ``DatabaseDataSource``
* - ``ActiveDataSource``
* - ``AdaptorDataSource``
*
* All of those datasources are very similiar in the operations they provide,
* but they differ in the feature set and overhead.
*
* `DatabaseDataSource` works on top of an `EditingContext`. It has the biggest
* overhead but provides features like object uniquing/registry. Eg if you need
* to fetch a bunch of objects and then perform subsequent processing on them
* (for example permission checks), it is convenient because the context
* remembers the fetched objects. This datasource returns DatabaseObject's as
* specified in the associated Model.
* ``DatabaseDataSource`` works on top of an ``ObjectTrackingContext``.
* It has the biggest overhead but provides features like object
* uniquing/registry.
* Eg if you need to fetch a bunch of objects and then perform subsequent
* processing on them (for example permission checks), it is convenient because
* the context remembers the fetched objects.
* This datasource returns ``DatabaseObject``'s as specified in the associated
* Model.
*
* `ActiveDataSource` is similiar to `DatabaseDataSource`, but it directly works
* on a channel. It has a reasonably small overhead and still provides a good
* feature set, like object mapping or prefetching.
* ``ActiveDataSource`` is similiar to ``DatabaseDataSource``, but it directly
* works on an ``DatabaseChannel``.
* It has a reasonably small overhead and still provides a good feature set,
* like object mapping or prefetching.
*
* Finally `AdaptorDataSource`. This datasource does not perform object mapping,
* that is, it returns `AdaptorRecord` objects and works directly on top of an
* `AdaptorChannel`.
* Finally ``AdaptorDataSource``. This datasource does not perform object
* mapping, that is, it returns ``AdaptorRecord`` objects and works directly on
* top of an ``AdaptorChannel``.
*/
open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {

Expand Down Expand Up @@ -98,7 +101,8 @@ open class AccessDataSource<Object: SwiftObject> : DataSource<Object> {
fatalError("implement in subclass: \(#function)")
}

override open func fetchObjects(cb yield: ( Object ) -> Void) throws {
override open func fetchObjects(yield: ( Object ) -> Void) throws {
// `iteratorForObjects` in GETobjects
try _primaryFetchObjects(try fetchSpecificationForFetch(), yield: yield)
}
override open func fetchCount() throws -> Int {
Expand Down
9 changes: 6 additions & 3 deletions Sources/ZeeQL/Access/AccessDataSourceFinders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ public extension AccessDataSource { // GIDs

func fetchObjects<S: Sequence>(with globalIDs: S,
yield: ( Object ) throws -> Void) throws
where S.Element : GlobalID
//where S.Element : GlobalID
where S.Element == KeyGlobalID
{
guard let entity = entity else { throw AccessDataSourceError.MissingEntity }
let gidQualifiers = globalIDs.map { entity.qualifierForGlobalID($0) }
Expand All @@ -123,14 +124,16 @@ public extension AccessDataSource { // GIDs
try fetchObjects(fs, cb: yield)
}
func fetchObjects<S: Sequence>(with globalIDs: S) throws -> [ Object ]
where S.Element : GlobalID
//where S.Element : GlobalID
where S.Element == KeyGlobalID
{
var objects = [ Object ]()
try fetchObjects(with: globalIDs) { objects.append($0) }
return objects
}
func fetchObjects<S: Collection>(with globalIDs: S) throws -> [ Object ]
where S.Element : GlobalID
//where S.Element : GlobalID
where S.Element == KeyGlobalID
{
var objects = [ Object ]()
objects.reserveCapacity(globalIDs.count)
Expand Down
12 changes: 8 additions & 4 deletions Sources/ZeeQL/Access/ActiveDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ZeeQL
//
// Created by Helge Hess on 27/02/17.
// Copyright © 2017 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
Expand Down Expand Up @@ -38,6 +38,7 @@ open class ActiveDataSource<Object: ActiveRecordType> : AccessDataSource<Object>
self.log = database.log
}

@inlinable
public convenience init(database: Database) {
if let t = Object.self as? EntityType.Type {
self.init(database: database, entity: t.entity)
Expand All @@ -50,12 +51,14 @@ open class ActiveDataSource<Object: ActiveRecordType> : AccessDataSource<Object>

// MARK: - Create

@inlinable
open func createObject() -> Object {
let object = Object()
object.bind(to: database, entity: entity)
return object
}

@inlinable
open func insertObject(_ object: Object) throws {
object.bind(to: database, entity: entity)
try object.save()
Expand Down Expand Up @@ -211,9 +214,10 @@ fileprivate let countAttr : Attribute = {

public extension Database {

func datasource<Object>(_ type: Object.Type) -> ActiveDataSource<Object> {
// use type argument to capture, is there a nicer way?
@inlinable
func datasource<Object>(_ type: Object.Type = Object.self)
-> ActiveDataSource<Object>
{
return ActiveDataSource<Object>(database: self)
}

}
28 changes: 16 additions & 12 deletions Sources/ZeeQL/Access/ActiveRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ZeeQL
//
// Created by Helge Hess on 26/02/2017.
// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
Expand Down Expand Up @@ -123,11 +123,16 @@ open class ActiveRecordBase : ActiveRecordType, SmartDescription {


// MARK: - KVC
// TBD: the KVC and StoredKVC are very similar, but not the same? They should
// be the same as they perform no extra work here?

open func takeValue(_ value: Any?, forKey k: String) throws {
// Note: `values` itself is a [String:Any?], so all that may be superfluous.
willChange()

// Note: There is no default setter! This is why this always pushes into
// the dictionary!
// TODO: Since the stable v5 ABI we *can* do KVC setters, implement them!
if let value = value {
values[k] = value // values is wrapped again in an Optional<Any>
}
Expand Down Expand Up @@ -168,43 +173,42 @@ open class ActiveRecordBase : ActiveRecordType, SmartDescription {

public func addObject(_ object: AnyObject, toPropertyWithKey key: String) {
// TBD: this is, sigh.
// also, the KVC access is still a little open, this should do
// takeValueForKey in case the subclass overrides it

willChange()

// If it is a to-one, we push the object itself into the relship.
if let relship = entity[relationship: key], !relship.isToMany {
values[key] = object
takeStoredValue(object, forKey: key)
return
}

if var list = values[key] as? [ AnyObject ] {
// TBD: Really AnyObject? Rather `DatabaseObject`?
// Because the input is like that!
if var list = storedValue(forKey: key) as? [ AnyObject ] {
list.append(object)
values[key] = list
takeStoredValue(list, forKey: key)
}
else {
values[key] = [ object ]
takeStoredValue([ object ], forKey: key)
}
}
public func removeObject(_ o: AnyObject, fromPropertyWithKey key: String) {
willChange()

// If it is a to-one, we clear the object itself into the relship.
if let relship = entity[relationship: key], !relship.isToMany {
values.removeValue(forKey: key)
takeStoredValue(nil, forKey: key)
return
}

guard var list = values[key] as? [ AnyObject ] else { return }
guard var list = storedValue(forKey: key) as? [ AnyObject ] else { return }
#if swift(>=5)
guard let idx = list.firstIndex(where: { $0 === o }) else { return }
#else
guard let idx = list.index(where: { $0 === o }) else { return }
#endif

list.remove(at: idx)
values[key] = [ list ]
takeStoredValue([ list ], forKey: key)
}


Expand Down Expand Up @@ -283,7 +287,7 @@ open class ActiveRecordBase : ActiveRecordType, SmartDescription {

// MARK: - Description

public func appendToDescription(_ ms: inout String) {
open func appendToDescription(_ ms: inout String) {
ms += " [\(entity.name)]"

if isNew { ms += " NEW" }
Expand Down
31 changes: 21 additions & 10 deletions Sources/ZeeQL/Access/AdaptorChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// ZeeQL
//
// Created by Helge Hess on 24/02/17.
// Copyright © 2017 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
* Adaptor channels represent database connections. Usually subclassed by
* specific adaptors (e.g. there is a SQLite3AdaptorChannel) to implement
* Adaptor channels represent database connections. Usually implemented by
* specific adaptors (e.g. there is a ``SQLite3AdaptorChannel``) to implement
* the operations.
*/
public protocol AdaptorChannel : AdaptorQueryType, ModelNameMapper {
Expand Down Expand Up @@ -215,6 +215,7 @@ public extension AdaptorChannel {
* - parameters:
* - op: the `AdaptorOperation` object
*/
@inlinable
func performAdaptorOperation(_ op: AdaptorOperation) throws {
let affectedRows = try performAdaptorOperationN(op)
guard affectedRows == 1 else {
Expand Down Expand Up @@ -434,6 +435,7 @@ public extension AdaptorChannel {
}
}

@inlinable
func columnNameForAttribute(_ a: Attribute) -> String {
if let c = a.columnName { return c }
// TODO: derive column-name based on some configurable algorithm, e.g.
Expand All @@ -451,6 +453,7 @@ public extension AdaptorChannel {
* - entity: the entity which should be updated
* - returns: number of affected rows or -1 on error
*/
@inlinable
func updateValuesInRowsDescribedByQualifier(_ values : [ String: Any? ],
_ qualifier : Qualifier,
_ entity : Entity) throws
Expand All @@ -461,6 +464,7 @@ public extension AdaptorChannel {
return try evaluateUpdateExpression(expr)
}

@inlinable
func deleteRowsDescribedByQualifier(_ q: Qualifier, _ e: Entity) throws
-> Int
{
Expand All @@ -477,6 +481,7 @@ public extension AdaptorChannel {
* - entity: the entity which contains the row
* - returns: true if exactly one row was deleted, false otherwise
*/
@inlinable
func deleteRowDescribedByQualifier(_ qualifier: Qualifier, _ entity: Entity)
throws -> Bool
{
Expand Down Expand Up @@ -550,11 +555,12 @@ public extension AdaptorChannel {
* The method returns true if exactly one row was affected by the SQL
* statement. If the operation failed the error is thrown.
*
* - parameters:
* - Parameters:
* - row: the record which should be inserted
* - entity: optionally an entity representing the table
* - returns: the record, potentially refetched and updated
* - Returns: the record, potentially refetched and updated
*/
@inlinable
func insertRow(_ row: AdaptorRow, _ entity: Entity? = nil)
throws -> AdaptorRow
{
Expand All @@ -570,10 +576,11 @@ extension AdaptorChannel {
* Scans the given array for attributes whose name does not match their
* external name (the database column).
*
* - parameters:
* - Parameters:
* - attributes: the attributes array to be checked
* - returns: an array of attributes which need to be mapped
* - Returns: an array of attributes which need to be mapped
*/
@inlinable
func attributesWhichRequireRowNameMapping(_ attributes: [ Attribute ])
-> [ Attribute ]
{
Expand All @@ -592,6 +599,7 @@ public extension AdaptorChannel { // Utility

/* utility */

@inlinable
func fetchSingleStringRows(_ _sql: String, column: String) throws
-> [ String ]
{
Expand All @@ -609,15 +617,18 @@ public extension AdaptorChannel { // Utility

public extension AdaptorChannel {

func entityNameForTableName(_ tableName : String) -> String {
@inlinable
func entityNameForTableName(_ tableName: String) -> String {
return tableName
}

func attributeNameForColumnName(_ colName : String) -> String {
@inlinable
func attributeNameForColumnName(_ colName: String) -> String {
return colName
}

func describeModelWithTableNames(_ tableNames : [ String ]) throws -> Model? {
@inlinable
func describeModelWithTableNames(_ tableNames: [ String ]) throws -> Model? {
guard !tableNames.isEmpty else { return nil }

var entities = [ Entity ]()
Expand Down
Loading

0 comments on commit 44644a8

Please sign in to comment.