From 00cac8427ca78cb46b6f13255b52876646e4c537 Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Wed, 10 Jun 2020 12:45:11 -0400 Subject: [PATCH] Initial commit. --- .bazelrc | 2 + .gitignore | 6 + WORKSPACE | 54 ++ generate_xcodeproj.sh | 50 ++ src/BUILD | 44 ++ src/Dflat.swift | 6 + src/DflatAtom.swift | 7 + src/DflatChangeRequest.swift | 4 + src/DflatFetchedResult.swift | 21 + src/DflatQueryBuilder.swift | 27 + src/DflatTransactionContext.swift | 3 + src/Expr.swift | 24 + src/FlatBuffers/ByteBuffer.swift | 345 +++++++++++ src/FlatBuffers/Constants.swift | 99 +++ src/FlatBuffers/FlatBufferBuilder.swift | 565 ++++++++++++++++++ src/FlatBuffers/FlatBufferObject.swift | 30 + src/FlatBuffers/FlatBuffersUtils.swift | 16 + src/FlatBuffers/Int+extension.swift | 31 + src/FlatBuffers/Message.swift | 41 ++ src/FlatBuffers/Mutable.swift | 68 +++ src/FlatBuffers/Offset.swift | 12 + src/FlatBuffers/Struct.swift | 16 + src/FlatBuffers/Table.swift | 144 +++++ src/exprs/Addition.swift | 31 + src/exprs/And.swift | 62 ++ src/exprs/EqualTo.swift | 31 + src/exprs/Field.swift | 69 +++ src/exprs/GreaterThan.swift | 31 + src/exprs/GreaterThanOrEqualTo.swift | 31 + src/exprs/In.swift | 22 + src/exprs/IsNotNull.swift | 20 + src/exprs/IsNull.swift | 20 + src/exprs/LessThan.swift | 31 + src/exprs/LessThanOrEqualTo.swift | 31 + src/exprs/Mod.swift | 31 + src/exprs/Not.swift | 18 + src/exprs/NotEqualTo.swift | 31 + src/exprs/NotIn.swift | 22 + src/exprs/Or.swift | 36 ++ src/exprs/Subtraction.swift | 31 + src/exprs/Value.swift | 37 ++ src/sqlite/SQLiteConnection.swift | 56 ++ src/sqlite/SQLiteConnectionPool.swift | 58 ++ src/sqlite/SQLiteDflat.swift | 76 +++ src/sqlite/SQLiteDflatAtom.swift | 4 + src/sqlite/SQLiteDflatFetchedResult.swift | 15 + src/sqlite/SQLiteDflatQueryBuilder.swift | 23 + .../SQLiteDflatTransactionContext.swift | 8 + src/sqlite/SQLiteExpr.swift | 59 ++ src/sqlite/exprs/SQLiteAddition.swift | 17 + src/sqlite/exprs/SQLiteAnd.swift | 27 + src/sqlite/exprs/SQLiteEqualTo.swift | 17 + src/sqlite/exprs/SQLiteField.swift | 9 + src/sqlite/exprs/SQLiteGreaterThan.swift | 17 + .../exprs/SQLiteGreaterThanOrEqualTo.swift | 17 + src/sqlite/exprs/SQLiteIn.swift | 28 + src/sqlite/exprs/SQLiteIsNotNull.swift | 14 + src/sqlite/exprs/SQLiteIsNull.swift | 14 + src/sqlite/exprs/SQLiteLessThan.swift | 17 + .../exprs/SQLiteLessThanOrEqualTo.swift | 17 + src/sqlite/exprs/SQLiteMod.swift | 17 + src/sqlite/exprs/SQLiteNot.swift | 15 + src/sqlite/exprs/SQLiteNotEqualTo.swift | 17 + src/sqlite/exprs/SQLiteNotIn.swift | 28 + src/sqlite/exprs/SQLiteOr.swift | 17 + src/sqlite/exprs/SQLiteSubtraction.swift | 17 + src/sqlite/exprs/SQLiteValue.swift | 84 +++ src/tests/BUILD | 43 ++ src/tests/ExprTests.swift | 176 ++++++ src/tests/Monster.swift | 99 +++ src/tests/MonsterChangeRequest.swift | 0 src/tests/SQLiteDflatTests.swift | 49 ++ src/tests/monster_generated.swift | 182 ++++++ 73 files changed, 3437 insertions(+) create mode 100644 .bazelrc create mode 100644 WORKSPACE create mode 100755 generate_xcodeproj.sh create mode 100644 src/BUILD create mode 100644 src/Dflat.swift create mode 100644 src/DflatAtom.swift create mode 100644 src/DflatChangeRequest.swift create mode 100644 src/DflatFetchedResult.swift create mode 100644 src/DflatQueryBuilder.swift create mode 100644 src/DflatTransactionContext.swift create mode 100644 src/Expr.swift create mode 100644 src/FlatBuffers/ByteBuffer.swift create mode 100644 src/FlatBuffers/Constants.swift create mode 100644 src/FlatBuffers/FlatBufferBuilder.swift create mode 100644 src/FlatBuffers/FlatBufferObject.swift create mode 100644 src/FlatBuffers/FlatBuffersUtils.swift create mode 100644 src/FlatBuffers/Int+extension.swift create mode 100644 src/FlatBuffers/Message.swift create mode 100644 src/FlatBuffers/Mutable.swift create mode 100644 src/FlatBuffers/Offset.swift create mode 100644 src/FlatBuffers/Struct.swift create mode 100644 src/FlatBuffers/Table.swift create mode 100644 src/exprs/Addition.swift create mode 100644 src/exprs/And.swift create mode 100644 src/exprs/EqualTo.swift create mode 100644 src/exprs/Field.swift create mode 100644 src/exprs/GreaterThan.swift create mode 100644 src/exprs/GreaterThanOrEqualTo.swift create mode 100644 src/exprs/In.swift create mode 100644 src/exprs/IsNotNull.swift create mode 100644 src/exprs/IsNull.swift create mode 100644 src/exprs/LessThan.swift create mode 100644 src/exprs/LessThanOrEqualTo.swift create mode 100644 src/exprs/Mod.swift create mode 100644 src/exprs/Not.swift create mode 100644 src/exprs/NotEqualTo.swift create mode 100644 src/exprs/NotIn.swift create mode 100644 src/exprs/Or.swift create mode 100644 src/exprs/Subtraction.swift create mode 100644 src/exprs/Value.swift create mode 100644 src/sqlite/SQLiteConnection.swift create mode 100644 src/sqlite/SQLiteConnectionPool.swift create mode 100644 src/sqlite/SQLiteDflat.swift create mode 100644 src/sqlite/SQLiteDflatAtom.swift create mode 100644 src/sqlite/SQLiteDflatFetchedResult.swift create mode 100644 src/sqlite/SQLiteDflatQueryBuilder.swift create mode 100644 src/sqlite/SQLiteDflatTransactionContext.swift create mode 100644 src/sqlite/SQLiteExpr.swift create mode 100644 src/sqlite/exprs/SQLiteAddition.swift create mode 100644 src/sqlite/exprs/SQLiteAnd.swift create mode 100644 src/sqlite/exprs/SQLiteEqualTo.swift create mode 100644 src/sqlite/exprs/SQLiteField.swift create mode 100644 src/sqlite/exprs/SQLiteGreaterThan.swift create mode 100644 src/sqlite/exprs/SQLiteGreaterThanOrEqualTo.swift create mode 100644 src/sqlite/exprs/SQLiteIn.swift create mode 100644 src/sqlite/exprs/SQLiteIsNotNull.swift create mode 100644 src/sqlite/exprs/SQLiteIsNull.swift create mode 100644 src/sqlite/exprs/SQLiteLessThan.swift create mode 100644 src/sqlite/exprs/SQLiteLessThanOrEqualTo.swift create mode 100644 src/sqlite/exprs/SQLiteMod.swift create mode 100644 src/sqlite/exprs/SQLiteNot.swift create mode 100644 src/sqlite/exprs/SQLiteNotEqualTo.swift create mode 100644 src/sqlite/exprs/SQLiteNotIn.swift create mode 100644 src/sqlite/exprs/SQLiteOr.swift create mode 100644 src/sqlite/exprs/SQLiteSubtraction.swift create mode 100644 src/sqlite/exprs/SQLiteValue.swift create mode 100644 src/tests/BUILD create mode 100644 src/tests/ExprTests.swift create mode 100644 src/tests/Monster.swift create mode 100644 src/tests/MonsterChangeRequest.swift create mode 100644 src/tests/SQLiteDflatTests.swift create mode 100644 src/tests/monster_generated.swift diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 000000000..7d89a0bf1 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,2 @@ +build --strategy=ObjcLink=standalone +build --disk_cache=/tmp/bazel diff --git a/.gitignore b/.gitignore index 330d1674f..490adac44 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,9 @@ fastlane/test_output # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ + +*.xcodeproj +*.tulsiproj +*.mobileprovision +bazel-* +.DS_Store diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 000000000..0f2823cbc --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,54 @@ +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") + +git_repository( + name = "build_bazel_rules_apple", + remote = "https://github.com/bazelbuild/rules_apple.git", + tag = "0.19.0", +) + +git_repository( + name = "build_bazel_rules_swift", + remote = "https://github.com/bazelbuild/rules_swift.git", + tag = "0.13.0", +) + +git_repository( + name = "build_bazel_apple_support", + remote = "https://github.com/bazelbuild/apple_support.git", + tag = "0.7.2", +) + +git_repository( + name = "bazel_skylib", + remote = "https://github.com/bazelbuild/bazel-skylib.git", + tag = "0.9.0", +) + +load( + "@build_bazel_rules_swift//swift:repositories.bzl", + "swift_rules_dependencies", +) + +swift_rules_dependencies() + +load( + "@build_bazel_apple_support//lib:repositories.bzl", + "apple_support_dependencies", +) + +apple_support_dependencies() + +load( + "@com_google_protobuf//:protobuf_deps.bzl", + "protobuf_deps", +) + +protobuf_deps() + +http_file( + name = "xctestrunner", + executable = 1, + sha256 = "8b7352f7414de4b54478563c90d55509030baa531696dfe9c4e1bf0617ee5eb0", + urls = ["https://github.com/google/xctestrunner/releases/download/0.2.12/ios_test_runner.par"], +) diff --git a/generate_xcodeproj.sh b/generate_xcodeproj.sh new file mode 100755 index 000000000..dcb12a691 --- /dev/null +++ b/generate_xcodeproj.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Copyright 2016 The Tulsi Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# Helper script to invoke Tulsi in commandline mode. +# The path to the Tulsi.app bundle may be provided though the TULSI_APP +# environment variable. If it is not, the script will attempt to find +# Tulsi using the first result returned by the Spotlight index. + +set -eu + +# If the TULSI_APP environment variable is set, use it. +if [[ "${TULSI_APP:-}" != "" ]]; then + readonly app_bundle_path="${TULSI_APP}" +else + readonly tulsi_bundle_id=com.google.Tulsi + app_bundle_path=$(mdfind kMDItemCFBundleIdentifier=${tulsi_bundle_id} | head -1) +fi + +if [[ "${app_bundle_path}" == "" ]]; then + if [[ -f "/Applications/Tulsi.app/Contents/MacOS/Tulsi" ]]; then + readonly app_bundle_path="/Applications/Tulsi.app" + else + echo "Tulsi.app could not be located. Please ensure that you have built\ + Tulsi and that it exists in an accessible location." + + exit 1 + fi +fi + +readonly tulsi_path="${app_bundle_path}/Contents/MacOS/Tulsi" + +if [[ $# == 0 ]]; then + exec "${tulsi_path}" -- -h +else + echo "Using Tulsi at ${app_bundle_path}" + exec "${tulsi_path}" -- "$@" +fi diff --git a/src/BUILD b/src/BUILD new file mode 100644 index 000000000..577adfa89 --- /dev/null +++ b/src/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "FlatBuffers", + module_name = "FlatBuffers", + srcs = glob(["FlatBuffers/*.swift"]), +) + +swift_library( + name = "Dflat", + module_name = "Dflat", + visibility = ["//visibility:public"], + srcs = [ + "Expr.swift", + "Dflat.swift", + "DflatAtom.swift", + "DflatChangeRequest.swift", + "DflatQueryBuilder.swift", + "DflatFetchedResult.swift", + "DflatTransactionContext.swift", + ] + glob(["exprs/*.swift"]), + deps = [ + ":FlatBuffers", + ] +) + +swift_library( + name = "SQLiteDflat", + module_name = "SQLiteDflat", + visibility = ["//visibility:public"], + srcs = [ + "sqlite/SQLiteConnection.swift", + "sqlite/SQLiteConnectionPool.swift", + "sqlite/SQLiteDflat.swift", + "sqlite/SQLiteDflatAtom.swift", + "sqlite/SQLiteDflatQueryBuilder.swift", + "sqlite/SQLiteDflatFetchedResult.swift", + "sqlite/SQLiteDflatTransactionContext.swift", + "sqlite/SQLiteExpr.swift", + ] + glob(["sqlite/exprs/*.swift"]), + deps = [ + ":Dflat" + ] +) diff --git a/src/Dflat.swift b/src/Dflat.swift new file mode 100644 index 000000000..ef8d2f7c0 --- /dev/null +++ b/src/Dflat.swift @@ -0,0 +1,6 @@ +public protocol Dflat { + typealias ChangesHandler = (_ transactionContext: DflatTransactionContext) -> Void + typealias CompletionHandler = (_ success: Bool) -> Void + func performChanges(_ anyPool: [Any.Type], changesHandler: @escaping ChangesHandler, completionHandler: CompletionHandler?) + func fetchFor(ofType: T.Type) -> DflatQueryBuilder +} diff --git a/src/DflatAtom.swift b/src/DflatAtom.swift new file mode 100644 index 000000000..83b97688f --- /dev/null +++ b/src/DflatAtom.swift @@ -0,0 +1,7 @@ +import FlatBuffers + +open class DflatAtom { + public final var _rowid: Int64 = -1 + public init() {} + public init(bb: ByteBuffer) {} +} diff --git a/src/DflatChangeRequest.swift b/src/DflatChangeRequest.swift new file mode 100644 index 000000000..da11dcc67 --- /dev/null +++ b/src/DflatChangeRequest.swift @@ -0,0 +1,4 @@ + +public protocol DflatChangeRequest { + +} diff --git a/src/DflatFetchedResult.swift b/src/DflatFetchedResult.swift new file mode 100644 index 000000000..a46692e3b --- /dev/null +++ b/src/DflatFetchedResult.swift @@ -0,0 +1,21 @@ +// Using abstract class so we can provide implementation for array. +open class DflatFetchedResult: RandomAccessCollection { + let array: [Element] + + public typealias Element = Element + public typealias Index = Int + public typealias Indices = Range + public typealias SubSequence = Array.SubSequence + public var endIndex: Index{ array.endIndex } + public var indices: Indices { array.indices } + public var startIndex: Index { array.startIndex } + public func formIndex(after i: inout Index) { array.formIndex(after: &i) } + public func formIndex(before i: inout Index) { array.formIndex(before: &i) } + public subscript(position: Index) -> Element { array[position] } + public subscript(x: Indices) -> SubSequence { array[x] } + + public init(_ array: [Element]) { + self.array = array + } + +} diff --git a/src/DflatQueryBuilder.swift b/src/DflatQueryBuilder.swift new file mode 100644 index 000000000..b204e9d15 --- /dev/null +++ b/src/DflatQueryBuilder.swift @@ -0,0 +1,27 @@ +import FlatBuffers + +public enum SortingOrder { + case ascending + case descending +} + +public protocol OrderBy { + var name: String { get } + var sortingOrder: SortingOrder { get } + func areInIncreasingOrder(_ a: FlatBufferObject, _ b: FlatBufferObject) -> Bool + func areInIncreasingOrder(_ a: DflatAtom, _ b: DflatAtom) -> Bool +} + +public enum Limit { + case noLimit + case limit(_: Int) +} + +// This can be converted to PAT if we can use `some`. That requires the whole Dflat object to be PAT such that the returned +// DflatQueryBuilder can be an associated type. +open class DflatQueryBuilder { + public init() {} + open func `where`(_ clause: T, limit: Limit = .noLimit, orderBy: [OrderBy] = []) -> DflatFetchedResult where T.ResultType == Bool { + fatalError() + } +} diff --git a/src/DflatTransactionContext.swift b/src/DflatTransactionContext.swift new file mode 100644 index 000000000..1734e0dce --- /dev/null +++ b/src/DflatTransactionContext.swift @@ -0,0 +1,3 @@ +public protocol DflatTransactionContext { + func submit(_: DflatChangeRequest) -> Bool +} diff --git a/src/Expr.swift b/src/Expr.swift new file mode 100644 index 000000000..00fec0e84 --- /dev/null +++ b/src/Expr.swift @@ -0,0 +1,24 @@ +import FlatBuffers + +// See And.swift discussion for why we need 3-value. +public enum IndexUsefulness { + case none + case partial + case full +} + +public protocol Expr { + associatedtype ResultType + func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) + func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness + var useScanToRefine: Bool { get } +} + +public extension Expr { + func evaluate(table: FlatBufferObject) -> (result: ResultType, unknown: Bool) { + evaluate(table: table, object: nil) + } + func evaluate(object: DflatAtom) -> (result: ResultType, unknown: Bool) { + evaluate(table: nil, object: object) + } +} diff --git a/src/FlatBuffers/ByteBuffer.swift b/src/FlatBuffers/ByteBuffer.swift new file mode 100644 index 000000000..52a1c7d48 --- /dev/null +++ b/src/FlatBuffers/ByteBuffer.swift @@ -0,0 +1,345 @@ +import Foundation + +@usableFromInline protocol Storage { + /// pointer to the start of the buffer object in memory + var memory: UnsafeMutableRawPointer { get set } + /// Capacity of UInt8 the buffer can hold + var capacity: Int { get set } + func copy(from ptr: UnsafeRawPointer, count: Int) + func initalize(for size: Int) +} + +public struct ByteBuffer { + + /// Storage is a container that would hold the memory pointer to solve the issue of + /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer) + @usableFromInline final class InternalStorage: Storage { + + /// pointer to the start of the buffer object in memory + @usableFromInline var memory: UnsafeMutableRawPointer + /// Capacity of UInt8 the buffer can hold + @usableFromInline var capacity: Int + + init(count: Int, alignment: Int) { + memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment) + capacity = count + } + + deinit { + memory.deallocate() + } + + @usableFromInline func copy(from ptr: UnsafeRawPointer, count: Int) { + memory.copyMemory(from: ptr, byteCount: count) + } + + @usableFromInline func initalize(for size: Int) { + memory.initializeMemory(as: UInt8.self, repeating: 0, count: size) + } + } + + @usableFromInline final class ExternalStorage: Storage { + + /// pointer to the start of the buffer object in memory + @usableFromInline var memory: UnsafeMutableRawPointer + /// Capacity of UInt8 the buffer can hold + @usableFromInline var capacity: Int + + init(memory: UnsafeMutableRawPointer, capacity: Int) { + self.memory = memory + self.capacity = capacity + } + + @usableFromInline func copy(from ptr: UnsafeRawPointer, count: Int) { + memory.copyMemory(from: ptr, byteCount: count) + } + + @usableFromInline func initalize(for size: Int) { + memory.initializeMemory(as: UInt8.self, repeating: 0, count: size) + } + } + + @usableFromInline var _storage: Storage + + /// The size of the elements written to the buffer + their paddings + private var _writerSize: Int = 0 + /// Aliginment of the current memory being written to the buffer + internal var alignment = 1 + /// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer + internal var writerIndex: Int { return _storage.capacity - _writerSize } + + /// Reader is the position of the current Writer Index (capacity - size) + public var reader: Int { return writerIndex } + /// Current size of the buffer + public var size: UOffset { return UOffset(_writerSize) } + /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason + public var memory: UnsafeMutableRawPointer { return _storage.memory } + /// Current capacity for the buffer + public var capacity: Int { return _storage.capacity } + + /// Constructor that creates a Flatbuffer object from a UInt8 + /// - Parameter bytes: Array of UInt8 + public init(bytes: [UInt8]) { + var b = bytes + _storage = InternalStorage(count: bytes.count, alignment: alignment) + _writerSize = _storage.capacity + b.withUnsafeMutableBytes { bufferPointer in + self._storage.copy(from: bufferPointer.baseAddress!, count: bytes.count) + } + } + + /// Constructor that creates a Flatbuffer from the Swift Data type object + /// - Parameter data: Swift data Object + public init(data: Data) { + var b = data + _storage = InternalStorage(count: data.count, alignment: alignment) + _writerSize = _storage.capacity + b.withUnsafeMutableBytes { bufferPointer in + self._storage.copy(from: bufferPointer.baseAddress!, count: data.count) + } + } + + /// Constructor that creates a Flatbuffer from unsafe memory region without copying + /// - Parameter assumingMemoryBound: The unsafe memory region + /// - Parameter capacity: The size of the given memory region + public init(assumingMemoryBound: UnsafeMutableRawPointer, capacity: Int) { + _storage = ExternalStorage(memory: assumingMemoryBound, capacity: capacity) + _writerSize = capacity + } + + /// Constructor that creates a Flatbuffer instance with a size + /// - Parameter size: Length of the buffer + init(initialSize size: Int) { + let size = size.convertToPowerofTwo + _storage = InternalStorage(count: size, alignment: alignment) + _storage.initalize(for: size) + } + +#if swift(>=5.0) + /// Constructor that creates a Flatbuffer object from a ContiguousBytes + /// - Parameters: + /// - contiguousBytes: Binary stripe to use as the buffer + /// - count: amount of readable bytes + public init( + contiguousBytes: Bytes, + count: Int + ) { + _storage = InternalStorage(count: count, alignment: alignment) + _writerSize = _storage.capacity + contiguousBytes.withUnsafeBytes { buf in + _storage.copy(from: buf.baseAddress!, count: buf.count) + } + } +#endif + + /// Creates a copy of the buffer that's being built by calling sizedBuffer + /// - Parameters: + /// - memory: Current memory of the buffer + /// - count: count of bytes + internal init(memory: UnsafeMutableRawPointer, count: Int) { + _storage = InternalStorage(count: count, alignment: alignment) + _storage.copy(from: memory, count: count) + _writerSize = _storage.capacity + } + + /// Creates a copy of the existing flatbuffer, by copying it to a different memory. + /// - Parameters: + /// - memory: Current memory of the buffer + /// - count: count of bytes + /// - removeBytes: Removes a number of bytes from the current size + internal init(memory: UnsafeMutableRawPointer, count: Int, removing removeBytes: Int) { + _storage = InternalStorage(count: count, alignment: alignment) + _storage.copy(from: memory, count: count) + _writerSize = removeBytes + } + + /// Fills the buffer with padding by adding to the writersize + /// - Parameter padding: Amount of padding between two to be serialized objects + @usableFromInline mutating func fill(padding: UInt32) { + ensureSpace(size: padding) + _writerSize += (MemoryLayout.size * Int(padding)) + } + + ///Adds an array of type Scalar to the buffer memory + /// - Parameter elements: An array of Scalars + @usableFromInline mutating func push(elements: [T]) { + let size = elements.count * MemoryLayout.size + ensureSpace(size: UInt32(size)) + elements.lazy.reversed().forEach { (s) in + push(value: s, len: MemoryLayout.size(ofValue: s)) + } + } + + ///Adds an array of type Bool to the buffer memory + /// - Parameter elements: An array of Bool + @usableFromInline mutating func push(elements: [Bool]) { + let size = elements.count * MemoryLayout.size + ensureSpace(size: UInt32(size)) + elements.lazy.reversed().forEach { (s) in + push(value: s ? 1 : 0, len: MemoryLayout.size(ofValue: s)) + } + } + + /// A custom type of structs that are padded according to the flatbuffer padding, + /// - Parameters: + /// - value: Pointer to the object in memory + /// - size: Size of Value being written to the buffer + @usableFromInline mutating func push(struct value: UnsafeMutableRawPointer, size: Int) { + ensureSpace(size: UInt32(size)) + memcpy(_storage.memory.advanced(by: writerIndex - size), value, size) + defer { value.deallocate() } + _writerSize += size + } + + /// Adds an object of type Scalar into the buffer + /// - Parameters: + /// - value: Object that will be written to the buffer + /// - len: Offset to subtract from the WriterIndex + @usableFromInline mutating func push(value: T, len: Int) { + ensureSpace(size: UInt32(len)) + var v = value.convertedEndian + memcpy(_storage.memory.advanced(by: writerIndex - len), &v, len) + _writerSize += len + } + + /// Adds a string to the buffer using swift.utf8 object + /// - Parameter str: String that will be added to the buffer + /// - Parameter len: length of the string + @usableFromInline mutating func push(string str: String, len: Int) { + ensureSpace(size: UInt32(len)) + if str.utf8.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != nil { + } else { + let utf8View = str.utf8 + for c in utf8View.lazy.reversed() { + push(value: c, len: 1) + } + } + } + + /// Writes a string to Bytebuffer using UTF8View + /// - Parameters: + /// - bytes: Pointer to the view + /// - len: Size of string + @usableFromInline mutating internal func push(bytes: UnsafeBufferPointer, len: Int) -> Bool { + memcpy(_storage.memory.advanced(by: writerIndex - len), UnsafeRawPointer(bytes.baseAddress!), len) + _writerSize += len + return true + } + + /// Write stores an object into the buffer directly or indirectly. + /// + /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory + /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end + /// - Parameters: + /// - value: Value that needs to be written to the buffer + /// - index: index to write to + /// - direct: Should take into consideration the capacity of the buffer + func write(value: T, index: Int, direct: Bool = false) { + var index = index + if !direct { + index = _storage.capacity - index + } + _storage.memory.storeBytes(of: value, toByteOffset: index, as: T.self) + } + + /// Makes sure that buffer has enouch space for each of the objects that will be written into it + /// - Parameter size: size of object + @discardableResult + @usableFromInline mutating func ensureSpace(size: UInt32) -> UInt32 { + if Int(size) + _writerSize > _storage.capacity { reallocate(size) } + assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes") + return size + } + + /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer + /// - Parameter size: Size of the current object + @usableFromInline mutating internal func reallocate(_ size: UInt32) { + let currentWritingIndex = writerIndex + while _storage.capacity <= _writerSize + Int(size) { + _storage.capacity = _storage.capacity << 1 + } + + /// solution take from Apple-NIO + _storage.capacity = _storage.capacity.convertToPowerofTwo + + let newData = UnsafeMutableRawPointer.allocate(byteCount: _storage.capacity, alignment: alignment) + newData.initializeMemory(as: UInt8.self, repeating: 0, count: _storage.capacity) + newData + .advanced(by: writerIndex) + .copyMemory(from: _storage.memory.advanced(by: currentWritingIndex), byteCount: _writerSize) + _storage.memory.deallocate() + _storage.memory = newData + } + + /// Clears the current size of the buffer + mutating public func clearSize() { + _writerSize = 0 + } + + /// Clears the current instance of the buffer, replacing it with new memory + mutating public func clear() { + _writerSize = 0 + alignment = 1 + _storage.memory.deallocate() + _storage.memory = UnsafeMutableRawPointer.allocate(byteCount: _storage.capacity, alignment: alignment) + } + + /// Resizes the buffer size + /// - Parameter size: new size for the buffer + @usableFromInline mutating internal func resize(_ size: Int) { + assert((_writerSize - size) > 0) + var zero: UInt8 = 0 + for i in 0..<(_writerSize - size) { + memcpy(_storage.memory.advanced(by: writerIndex + i), &zero, MemoryLayout.size) + } + _writerSize = size + } + + /// Reads an object from the buffer + /// - Parameters: + /// - def: Type of the object + /// - position: the index of the object in the buffer + public func read(def: T.Type, position: Int) -> T { + return _storage.memory.advanced(by: position).load(as: T.self) + } + + /// Reads a slice from the memory assuming a type of T + /// - Parameters: + /// - index: index of the object to be read from the buffer + /// - count: count of bytes in memory + public func readSlice(index: Int32, + count: Int32) -> [T] { + let start = _storage.memory.advanced(by: Int(index)).assumingMemoryBound(to: T.self) + let array = UnsafeBufferPointer(start: start, count: Int(count)) + return Array(array) + } + + /// Reads a string from the buffer and encodes it to a swift string + /// - Parameters: + /// - index: index of the string in the buffer + /// - count: length of the string + /// - type: Encoding of the string + public func readString(at index: Int32, + count: Int32, + type: String.Encoding = .utf8) -> String? { + let start = _storage.memory.advanced(by: Int(index)).assumingMemoryBound(to: UInt8.self) + let bufprt = UnsafeBufferPointer(start: start, count: Int(count)) + return String(bytes: Array(bufprt), encoding: type) + } + + /// Creates a new Flatbuffer object that's duplicated from the current one + /// - Parameter removeBytes: the amount of bytes to remove from the current Size + public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer { + return ByteBuffer(memory: _storage.memory, count: _storage.capacity, removing: _writerSize - removeBytes) + } +} + +extension ByteBuffer: CustomDebugStringConvertible { + + public var debugDescription: String { + """ + buffer located at: \(_storage.memory), with capacity of \(_storage.capacity) + { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) } + """ + } +} diff --git a/src/FlatBuffers/Constants.swift b/src/FlatBuffers/Constants.swift new file mode 100644 index 000000000..03ea398b1 --- /dev/null +++ b/src/FlatBuffers/Constants.swift @@ -0,0 +1,99 @@ +#if os(Linux) +import CoreFoundation +#else +import Foundation +#endif + +/// A boolean to see if the system is littleEndian +let isLitteEndian = CFByteOrderGetCurrent() == Int(CFByteOrderLittleEndian.rawValue) +/// Constant for the file id length +let FileIdLength = 4 +/// Type aliases +public typealias Byte = UInt8 +public typealias UOffset = UInt32 +public typealias SOffset = Int32 +public typealias VOffset = UInt16 +/// Maximum size for a buffer +public let FlatBufferMaxSize = UInt32.max << ((MemoryLayout.size * 8 - 1) - 1) + +/// Protocol that confirms all the numbers +/// +/// Scalar is used to confirm all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer. +public protocol Scalar: Equatable { + associatedtype NumericValue + var convertedEndian: NumericValue { get } +} + +extension Scalar where Self: FixedWidthInteger { + /// Converts the value from BigEndian to LittleEndian + /// + /// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet. + public var convertedEndian: NumericValue { + if isLitteEndian { return self as! Self.NumericValue } + fatalError("This is not tested! please report an issue on the offical flatbuffers repo") + } +} + +extension Double: Scalar { + public typealias NumericValue = UInt64 + + public var convertedEndian: UInt64 { + if isLitteEndian { return self.bitPattern } + return self.bitPattern.littleEndian + } +} + +extension Float32: Scalar { + public typealias NumericValue = UInt32 + + public var convertedEndian: UInt32 { + if isLitteEndian { return self.bitPattern } + return self.bitPattern.littleEndian + } +} + +extension Bool: Scalar { + public var convertedEndian: UInt8 { + return self == true ? 1 : 0 + } + + public typealias NumericValue = UInt8 +} + +extension Int: Scalar { + public typealias NumericValue = Int +} + +extension Int8: Scalar { + public typealias NumericValue = Int8 +} + +extension Int16: Scalar { + public typealias NumericValue = Int16 +} + +extension Int32: Scalar { + public typealias NumericValue = Int32 +} + +extension Int64: Scalar { + public typealias NumericValue = Int64 +} + +extension UInt8: Scalar { + public typealias NumericValue = UInt8 +} + +extension UInt16: Scalar { + public typealias NumericValue = UInt16 +} + +extension UInt32: Scalar { + public typealias NumericValue = UInt32 +} + +extension UInt64: Scalar { + public typealias NumericValue = UInt64 +} + +public func FlatBuffersVersion_1_12_0() {} diff --git a/src/FlatBuffers/FlatBufferBuilder.swift b/src/FlatBuffers/FlatBufferBuilder.swift new file mode 100644 index 000000000..cd2523f9a --- /dev/null +++ b/src/FlatBuffers/FlatBufferBuilder.swift @@ -0,0 +1,565 @@ +import Foundation + +public struct FlatBufferBuilder { + + /// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable + @usableFromInline internal var _vtableStorage = VTableStorage() + + /// Reference Vtables that were already written to the buffer + private var _vtables: [UOffset] = [] + /// Flatbuffer data will be written into + private var _bb: ByteBuffer + /// A check if the buffer is being written into by a different table + private var isNested = false + /// Dictonary that stores a map of all the strings that were written to the buffer + private var stringOffsetMap: [String: Offset] = [:] + /// A check to see if finish(::) was ever called to retreive data object + private var finished = false + /// A check to see if the buffer should serialize Default values + private var serializeDefaults: Bool + + /// Current alignment for the buffer + var _minAlignment: Int = 0 { + didSet { + _bb.alignment = _minAlignment + } + } + + /// Gives a read access to the buffer's size + public var size: UOffset { return _bb.size } + /// Data representation of the buffer + public var data: Data { + assert(finished, "Data shouldn't be called before finish()") + return Data(bytes: _bb.memory.advanced(by: _bb.writerIndex), + count: _bb.capacity - _bb.writerIndex) + } + /// Get's the fully sized buffer stored in memory + public var fullSizedByteArray: [UInt8] { + let ptr = UnsafeBufferPointer(start: _bb.memory.assumingMemoryBound(to: UInt8.self), + count: _bb.capacity) + return Array(ptr) + } + /// Returns the written size of the buffer + public var sizedByteArray: [UInt8] { + let cp = _bb.capacity - _bb.writerIndex + let start = _bb.memory.advanced(by: _bb.writerIndex) + .bindMemory(to: UInt8.self, capacity: cp) + + let ptr = UnsafeBufferPointer(start: start, count: cp) + return Array(ptr) + } + /// Returns the buffer + public var buffer: ByteBuffer { return _bb } + + /// Returns A sized Buffer from the readable bytes + public var sizedBuffer: ByteBuffer { + assert(finished, "Data shouldn't be called before finish()") + return ByteBuffer(memory: _bb.memory.advanced(by: _bb.reader), count: Int(_bb.size)) + } + + // MARK: - Init + + /// initialize the buffer with a size + /// - Parameters: + /// - initialSize: Initial size for the buffer + /// - force: Allows default to be serialized into the buffer + public init(initialSize: Int32 = 1024, serializeDefaults force: Bool = false) { + assert(initialSize > 0, "Size should be greater than zero!") + guard isLitteEndian else { + fatalError("Reading/Writing a buffer in big endian machine is not supported on swift") + } + serializeDefaults = force + _bb = ByteBuffer(initialSize: Int(initialSize)) + } + + /// Clears the buffer and the builder from it's data + mutating public func clear() { + _minAlignment = 0 + isNested = false + stringOffsetMap = [:] + _vtables = [] + _vtableStorage.clear() + _bb.clear() + } + + // MARK: - Create Tables + + /// Checks if the required fields were serialized into the buffer + /// - Parameters: + /// - table: offset for the table + /// - fields: Array of all the important fields to be serialized + mutating public func require(table: Offset, fields: [Int32]) { + for field in fields { + let start = _bb.capacity - Int(table.o) + let startTable = start - Int(_bb.read(def: Int32.self, position: start)) + let isOkay = _bb.read(def: VOffset.self, position: startTable + Int(field)) != 0 + assert(isOkay, "Flatbuffers requires the following field") + } + } + + /// Finished the buffer by adding the file id and then calling finish + /// - Parameters: + /// - offset: Offset of the table + /// - fileId: Takes the fileId + /// - prefix: if false it wont add the size of the buffer + mutating public func finish(offset: Offset, fileId: String, addPrefix prefix: Bool = false) { + let size = MemoryLayout.size + preAlign(len: size + (prefix ? size : 0) + FileIdLength, alignment: _minAlignment) + assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4") + _bb.push(string: fileId, len: 4) + finish(offset: offset, addPrefix: prefix) + } + + /// Finished the buffer by adding the file id, offset, and prefix to it. + /// - Parameters: + /// - offset: Offset of the table + /// - prefix: if false it wont add the size of the buffer + mutating public func finish(offset: Offset, addPrefix prefix: Bool = false) { + notNested() + let size = MemoryLayout.size + preAlign(len: size + (prefix ? size : 0), alignment: _minAlignment) + push(element: refer(to: offset.o)) + if prefix { push(element: _bb.size) } + _vtableStorage.clear() + finished = true + } + + /// starttable will let the builder know, that a new object is being serialized. + /// + /// The function will fatalerror if called while there is another object being serialized + /// - Parameter numOfFields: Number of elements to be written to the buffer + mutating public func startTable(with numOfFields: Int) -> UOffset { + notNested() + isNested = true + _vtableStorage.start(count: numOfFields) + return _bb.size + } + + + /// Endtable will let the builder know that the object that's written to it is completed + /// + /// This would be called after all the elements are serialized, it will add the vtable into the buffer. + /// it will fatalError in case the object is called without starttable, or the object has exceeded the limit of + /// 2GB, + /// - Parameter startOffset:Start point of the object written + /// - returns: The root of the table + mutating public func endTable(at startOffset: UOffset) -> UOffset { + assert(isNested, "Calling endtable without calling starttable") + let sizeofVoffset = MemoryLayout.size + let vTableOffset = push(element: SOffset(0)) + + let tableObjectSize = vTableOffset - startOffset + assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes") + let _max = UInt32(_vtableStorage.maxOffset) + UInt32(sizeofVoffset) + + _bb.fill(padding: _max) + _bb.write(value: VOffset(tableObjectSize), index: _bb.writerIndex + sizeofVoffset, direct: true) + _bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true) + + for index in stride(from: 0, to: _vtableStorage.writtenIndex, by: _vtableStorage.size) { + let loaded = _vtableStorage.load(at: index) + guard loaded.offset != 0 else { continue } + let _index = (_bb.writerIndex + Int(loaded.position)) + _bb.write(value: VOffset(vTableOffset - loaded.offset), index: _index, direct: true) + } + + _vtableStorage.clear() + let vt_use = _bb.size + + var isAlreadyAdded: Int? + + let vt2 = _bb.memory.advanced(by: _bb.writerIndex) + let len2 = vt2.load(fromByteOffset: 0, as: Int16.self) + + for table in _vtables { + let position = _bb.capacity - Int(table) + let vt1 = _bb.memory.advanced(by: position) + let len1 = _bb.read(def: Int16.self, position: position) + if (len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2))) { continue } + + isAlreadyAdded = Int(table) + break + } + + if let offset = isAlreadyAdded { + let vTableOff = Int(vTableOffset) + let space = _bb.capacity - vTableOff + _bb.write(value: Int32(offset - vTableOff), index: space, direct: true) + _bb.resize(_bb.capacity - space) + } else { + _bb.write(value: Int32(vt_use) - Int32(vTableOffset), index: Int(vTableOffset)) + _vtables.append(_bb.size) + } + isNested = false + return vTableOffset + } + + // MARK: - Builds Buffer + + /// asserts to see if the object is not nested + @usableFromInline mutating internal func notNested() { + assert(!isNested, "Object serialization must not be nested") + } + + /// Changes the minimuim alignment of the buffer + /// - Parameter size: size of the current alignment + @usableFromInline mutating internal func minAlignment(size: Int) { + if size > _minAlignment { + _minAlignment = size + } + } + + /// Gets the padding for the current element + /// - Parameters: + /// - bufSize: Current size of the buffer + the offset of the object to be written + /// - elementSize: Element size + @usableFromInline mutating internal func padding(bufSize: UInt32, elementSize: UInt32) -> UInt32 { + ((~bufSize) &+ 1) & (elementSize - 1) + } + + /// Prealigns the buffer before writting a new object into the buffer + /// - Parameters: + /// - len:Length of the object + /// - alignment: Alignment type + @usableFromInline mutating internal func preAlign(len: Int, alignment: Int) { + minAlignment(size: alignment) + _bb.fill(padding: padding(bufSize: _bb.size + UOffset(len), elementSize: UOffset(alignment))) + } + + /// Prealigns the buffer before writting a new object into the buffer + /// - Parameters: + /// - len: Length of the object + /// - type: Type of the object to be written + @usableFromInline mutating internal func preAlign(len: Int, type: T.Type) { + preAlign(len: len, alignment: MemoryLayout.size) + } + + /// Refers to an object that's written in the buffer + /// - Parameter off: the objects index value + @usableFromInline mutating internal func refer(to off: UOffset) -> UOffset { + let size = MemoryLayout.size + preAlign(len: size, alignment: size) + return _bb.size - off + UInt32(size) + } + + /// Tracks the elements written into the buffer + /// - Parameters: + /// - offset: The offset of the element witten + /// - position: The position of the element + @usableFromInline mutating internal func track(offset: UOffset, at position: VOffset) { + _vtableStorage.add(loc: FieldLoc(offset: offset, position: position)) + } + + // MARK: - Vectors + + /// Starts a vector of length and Element size + mutating public func startVector(_ len: Int, elementSize: Int) { + notNested() + isNested = true + preAlign(len: len * elementSize, type: UOffset.self) + preAlign(len: len * elementSize, alignment: elementSize) + } + + /// Ends the vector of at length + /// + /// The current function will fatalError if startVector is called before serializing the vector + /// - Parameter len: Length of the buffer + mutating public func endVector(len: Int) -> UOffset { + assert(isNested, "Calling endVector without calling startVector") + isNested = false + return push(element: Int32(len)) + } + + /// Creates a vector of type Scalar in the buffer + /// - Parameter elements: elements to be written into the buffer + /// - returns: Offset of the vector + mutating public func createVector(_ elements: [T]) -> Offset { + return createVector(elements, size: elements.count) + } + + /// Creates a vector of type Scalar in the buffer + /// - Parameter elements: Elements to be written into the buffer + /// - Parameter size: Count of elements + /// - returns: Offset of the vector + mutating public func createVector(_ elements: [T], size: Int) -> Offset { + let size = size + startVector(size, elementSize: MemoryLayout.size) + _bb.push(elements: elements) + return Offset(offset: endVector(len: size)) + } + + /// Creates a vector of type Enums in the buffer + /// - Parameter elements: elements to be written into the buffer + /// - returns: Offset of the vector + mutating public func createVector(_ elements: [T]) -> Offset { + return createVector(elements, size: elements.count) + } + + /// Creates a vector of type Enums in the buffer + /// - Parameter elements: Elements to be written into the buffer + /// - Parameter size: Count of elements + /// - returns: Offset of the vector + mutating public func createVector(_ elements: [T], size: Int) -> Offset { + let size = size + startVector(size, elementSize: T.byteSize) + for e in elements.lazy.reversed() { + _bb.push(value: e.value, len: T.byteSize) + } + return Offset(offset: endVector(len: size)) + } + + /// Creates a vector of type Offsets in the buffer + /// - Parameter offsets:Array of offsets of type T + /// - returns: Offset of the vector + mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset { + createVector(ofOffsets: offsets, len: offsets.count) + } + + /// Creates a vector of type Offsets in the buffer + /// - Parameter elements: Array of offsets of type T + /// - Parameter size: Count of elements + /// - returns: Offset of the vector + mutating public func createVector(ofOffsets offsets: [Offset], len: Int) -> Offset { + startVector(len, elementSize: MemoryLayout>.size) + for o in offsets.lazy.reversed() { + push(element: o) + } + return Offset(offset: endVector(len: len)) + } + + /// Creates a vector of Strings + /// - Parameter str: a vector of strings that will be written into the buffer + /// - returns: Offset of the vector + mutating public func createVector(ofStrings str: [String]) -> Offset { + var offsets: [Offset] = [] + for s in str { + offsets.append(create(string: s)) + } + return createVector(ofOffsets: offsets) + } + + /// Creates a vector of Flatbuffer structs. + /// + /// The function takes a Type to know what size it is, and alignment + /// - Parameters: + /// - structs: An array of UnsafeMutableRawPointer + /// - type: Type of the struct being written + /// - returns: Offset of the vector + mutating public func createVector(structs: [UnsafeMutableRawPointer], + type: T.Type) -> Offset { + startVector(structs.count * T.size, elementSize: T.alignment) + for i in structs.lazy.reversed() { + create(struct: i, type: T.self) + } + return Offset(offset: endVector(len: structs.count)) + } + + // MARK: - Inserting Structs + + /// Writes a Flatbuffer struct into the buffer + /// - Parameters: + /// - s: Flatbuffer struct + /// - type: Type of the element to be serialized + /// - returns: Offset of the Object + @discardableResult + mutating public func create(struct s: UnsafeMutableRawPointer, + type: T.Type) -> Offset { + let size = T.size + preAlign(len: size, alignment: T.alignment) + _bb.push(struct: s, size: size) + return Offset(offset: _bb.size) + } + + /// Adds the offset of a struct into the vTable + /// + /// The function fatalErrors if we pass an offset that is out of range + /// - Parameter o: offset + mutating public func add(structOffset o: VOffset) { + _vtableStorage.add(loc: FieldLoc(offset: _bb.size, position: VOffset(o))) + } + + // MARK: - Inserting Strings + + /// Insets a string into the buffer using UTF8 + /// - Parameter str: String to be serialized + /// - returns: The strings offset in the buffer + mutating public func create(string str: String) -> Offset { + let len = str.utf8.count + notNested() + preAlign(len: len + 1, type: UOffset.self) + _bb.fill(padding: 1) + _bb.push(string: str, len: len) + push(element: UOffset(len)) + return Offset(offset: _bb.size) + } + + /// Inserts a shared string to the buffer + /// + /// The function checks the stringOffsetmap if it's seen a similar string before + /// - Parameter str: String to be serialized + /// - returns: The strings offset in the buffer + mutating public func createShared(string str: String) -> Offset { + if let offset = stringOffsetMap[str] { + return offset + } + let offset = create(string: str) + stringOffsetMap[str] = offset + return offset + } + + // MARK: - Inseting offsets + + /// Adds the offset of an object into the buffer + /// - Parameters: + /// - offset: Offset of another object to be written + /// - position: The predefined position of the object + mutating public func add(offset: Offset, at position: VOffset) { + if offset.isEmpty { + track(offset: 0, at: position) + return + } + add(element: refer(to: offset.o), def: 0, at: position) + } + + /// Pushes a value of type offset into the buffer + /// - Parameter o: Offset + /// - returns: Position of the offset + @discardableResult + mutating public func push(element o: Offset) -> UOffset { + push(element: refer(to: o.o)) + } + + // MARK: - Inserting Scalars to Buffer + + /// Adds a value into the buffer of type Scalar + /// + /// - Parameters: + /// - element: Element to insert + /// - def: Default value for that element + /// - position: The predefined position of the element + mutating public func add(element: T, def: T, at position: VOffset) { + if (element == def && !serializeDefaults) { + track(offset: 0, at: position) + return + } + let off = push(element: element) + track(offset: off, at: position) + } + + /// Adds Boolean values into the buffer + /// - Parameters: + /// - condition: Condition to insert + /// - def: Default condition + /// - position: The predefined position of the element + @available(*, deprecated, message: "Deprecated, function will be removed in Flatbuffers v0.6.0. Regenerate code") + mutating public func add(condition: Bool, def: Bool, at position: VOffset) { + if (condition == def && !serializeDefaults) { + track(offset: 0, at: position) + return + } + let off = push(element: Byte(condition ? 1 : 0)) + track(offset: off, at: position) + } + + /// Pushes the values into the buffer + /// - Parameter element: Element to insert + /// - returns: Postion of the Element + @discardableResult + mutating public func push(element: T) -> UOffset { + preAlign(len: MemoryLayout.size, + alignment: MemoryLayout.size) + _bb.push(value: element, len: MemoryLayout.size) + return _bb.size + } + +} + +extension FlatBufferBuilder: CustomDebugStringConvertible { + + public var debugDescription: String { + """ + buffer debug: + \(_bb) + builder debug: + { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) } + """ + } + + /// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer + @usableFromInline internal class VTableStorage { + /// Memory check since deallocating each time we want to clear would be expensive + /// and memory leaks would happen if we dont deallocate the first allocated memory. + /// memory is promised to be available before adding `FieldLoc` + private var memoryInUse = false + /// Size of FieldLoc in memory + let size = MemoryLayout.stride + /// Memeory buffer + var memory: UnsafeMutableRawBufferPointer! + /// Capacity of the current buffer + var capacity: Int = 0 + /// Maximuim offset written to the class + var maxOffset: VOffset = 0 + /// number of fields written into the buffer + var numOfFields: Int = 0 + /// Last written Index + var writtenIndex: Int = 0 + /// the amount of added elements into the buffer + var addedElements: Int { return capacity - (numOfFields * size) } + + /// Creates the memory to store the buffer in + init() { + memory = UnsafeMutableRawBufferPointer.allocate(byteCount: 0, alignment: 0) + } + + deinit { + memory.deallocate() + } + + /// Builds a buffer with byte count of fieldloc.size * count of field numbers + /// - Parameter count: number of fields to be written + func start(count: Int) { + let capacity = count * size + ensure(space: capacity) + } + + /// Adds a FieldLoc into the buffer, which would track how many have been written, + /// and max offset + /// - Parameter loc: Location of encoded element + func add(loc: FieldLoc) { + memory.baseAddress?.advanced(by: writtenIndex).storeBytes(of: loc, as: FieldLoc.self) + writtenIndex += size + numOfFields += 1 + maxOffset = max(loc.position, maxOffset) + } + + /// Clears the data stored related to the encoded buffer + func clear() { + maxOffset = 0 + numOfFields = 0 + writtenIndex = 0 + } + + /// Ensure that the buffer has enough space instead of recreating the buffer each time. + /// - Parameter space: space required for the new vtable + func ensure(space: Int) { + guard space + writtenIndex > capacity else { return } + memory.deallocate() + memory = UnsafeMutableRawBufferPointer.allocate(byteCount: space, alignment: size) + capacity = space + } + + /// Loads an object of type `FieldLoc` from buffer memory + /// - Parameter index: index of element + /// - Returns: a FieldLoc at index + func load(at index: Int) -> FieldLoc { + return memory.load(fromByteOffset: index, as: FieldLoc.self) + } + + } + + internal struct FieldLoc { + var offset: UOffset + var position: VOffset + } + +} diff --git a/src/FlatBuffers/FlatBufferObject.swift b/src/FlatBuffers/FlatBufferObject.swift new file mode 100644 index 000000000..8fc23221a --- /dev/null +++ b/src/FlatBuffers/FlatBufferObject.swift @@ -0,0 +1,30 @@ +import Foundation + +/// FlatbufferObject structures all the Flatbuffers objects +public protocol FlatBufferObject { + var __buffer: ByteBuffer! { get } + init(_ bb: ByteBuffer, o: Int32) +} + +public protocol NativeTable {} + +public protocol ObjectAPI { + associatedtype T + static func pack(_ builder: inout FlatBufferBuilder, obj: inout T) -> Offset + mutating func unpack() -> T +} + +/// Readable is structures all the Flatbuffers structs +/// +/// Readable is a procotol that each Flatbuffer struct should confirm to since +/// FlatBufferBuilder would require a Type to both create(struct:) and createVector(structs:) functions +public protocol Readable: FlatBufferObject { + static var size: Int { get } + static var alignment: Int { get } +} + +public protocol Enum { + associatedtype T: Scalar + static var byteSize: Int { get } + var value: T { get } +} diff --git a/src/FlatBuffers/FlatBuffersUtils.swift b/src/FlatBuffers/FlatBuffersUtils.swift new file mode 100644 index 000000000..6838f8623 --- /dev/null +++ b/src/FlatBuffers/FlatBuffersUtils.swift @@ -0,0 +1,16 @@ +import Foundation + +public final class FlatBuffersUtils { + + /// Gets the size of the prefix + /// - Parameter bb: Flatbuffer object + public static func getSizePrefix(bb: ByteBuffer) -> Int32 { + return bb.read(def: Int32.self, position: bb.reader) + } + + /// Removes the prefix by duplicating the Flatbuffer + /// - Parameter bb: Flatbuffer object + public static func removeSizePrefix(bb: ByteBuffer) -> ByteBuffer { + return bb.duplicate(removing: MemoryLayout.size) + } +} diff --git a/src/FlatBuffers/Int+extension.swift b/src/FlatBuffers/Int+extension.swift new file mode 100644 index 000000000..e52bdab62 --- /dev/null +++ b/src/FlatBuffers/Int+extension.swift @@ -0,0 +1,31 @@ +import Foundation + +extension Int { + + /// Moves the current int into the nearest power of two + /// + /// This is used since the UnsafeMutableRawPointer will face issues when writing/reading + /// if the buffer alignment exceeds that actual size of the buffer + var convertToPowerofTwo: Int { + guard self > 0 else { return 1 } + var n = UOffset(self) + + #if arch(arm) || arch(i386) + let max = UInt32(Int.max) + #else + let max = UInt32.max + #endif + + n -= 1 + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + if n != max { + n += 1 + } + + return Int(n) + } +} diff --git a/src/FlatBuffers/Message.swift b/src/FlatBuffers/Message.swift new file mode 100644 index 000000000..590d3d7fc --- /dev/null +++ b/src/FlatBuffers/Message.swift @@ -0,0 +1,41 @@ +public protocol FlatBufferGRPCMessage { + + /// Raw pointer which would be pointing to the beginning of the readable bytes + var rawPointer: UnsafeMutableRawPointer { get } + + /// Size of readable bytes in the buffer + var size: Int { get } + + init(byteBuffer: ByteBuffer) +} + +/// Message is a wrapper around Buffers to to able to send Flatbuffers `Buffers` through the +/// GRPC library +public final class Message: FlatBufferGRPCMessage { + internal var buffer: ByteBuffer + + /// Returns the an object of type T that would be read from the buffer + public var object: T { + T.init(buffer, o: Int32(buffer.read(def: UOffset.self, position: buffer.reader)) + Int32(buffer.reader)) + } + + public var rawPointer: UnsafeMutableRawPointer { return buffer.memory.advanced(by: buffer.reader) } + + public var size: Int { return Int(buffer.size) } + + /// Initializes the message with the type Flatbuffer.Bytebuffer that is transmitted over + /// GRPC + /// - Parameter byteBuffer: Flatbuffer ByteBuffer object + public init(byteBuffer: ByteBuffer) { + buffer = byteBuffer + } + + /// Initializes the message by copying the buffer to the message to be sent. + /// from the builder + /// - Parameter builder: FlatbufferBuilder that has the bytes created in + /// - Note: Use `builder.finish(offset)` before passing the builder without prefixing anything to it + public init(builder: inout FlatBufferBuilder) { + buffer = builder.sizedBuffer + builder.clear() + } +} diff --git a/src/FlatBuffers/Mutable.swift b/src/FlatBuffers/Mutable.swift new file mode 100644 index 000000000..90c1d8b10 --- /dev/null +++ b/src/FlatBuffers/Mutable.swift @@ -0,0 +1,68 @@ +import Foundation + +/// Mutable is a protocol that allows us to mutate Scalar values within the buffer +public protocol Mutable { + /// makes Flatbuffer accessed within the Protocol + var bb: ByteBuffer { get } + /// makes position of the table/struct accessed within the Protocol + var postion: Int32 { get } +} + +extension Mutable { + + /// Mutates the memory in the buffer, this is only called from the access function of table and structs + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + func mutate(value: T, o: Int32) -> Bool { + guard o != 0 else { return false } + bb.write(value: value, index: Int(o), direct: true) + return true + } +} + +extension Mutable where Self == Table { + + /// Mutates a value by calling mutate with respect to the position in the table + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func mutate(_ value: T, index: Int32) -> Bool { + guard index != 0 else { return false } + return mutate(value: value, o: index + postion) + } + + /// Directly mutates the element by calling mutate + /// + /// Mutates the Element at index ignoring the current position by calling mutate + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func directMutate(_ value: T, index: Int32) -> Bool { + return mutate(value: value, o: index) + } +} + +extension Mutable where Self == Struct { + + /// Mutates a value by calling mutate with respect to the position in the struct + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func mutate(_ value: T, index: Int32) -> Bool { + return mutate(value: value, o: index + postion) + } + + /// Directly mutates the element by calling mutate + /// + /// Mutates the Element at index ignoring the current position by calling mutate + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func directMutate(_ value: T, index: Int32) -> Bool { + return mutate(value: value, o: index) + } +} + +extension Struct: Mutable {} +extension Table: Mutable {} diff --git a/src/FlatBuffers/Offset.swift b/src/FlatBuffers/Offset.swift new file mode 100644 index 000000000..cdb02278b --- /dev/null +++ b/src/FlatBuffers/Offset.swift @@ -0,0 +1,12 @@ +import Foundation + +/// Offset object for all the Objects that are written into the buffer +public struct Offset { + /// Offset of the object in the buffer + public var o: UOffset + /// Returns false if the offset is equal to zero + public var isEmpty: Bool { return o == 0 } + + public init(offset: UOffset) { o = offset } + public init() { o = 0 } +} diff --git a/src/FlatBuffers/Struct.swift b/src/FlatBuffers/Struct.swift new file mode 100644 index 000000000..88e3a41a2 --- /dev/null +++ b/src/FlatBuffers/Struct.swift @@ -0,0 +1,16 @@ +import Foundation + +public struct Struct { + public private(set) var bb: ByteBuffer + public private(set) var postion: Int32 + + public init(bb: ByteBuffer, position: Int32 = 0) { + self.bb = bb + self.postion = position + } + + public func readBuffer(of type: T.Type, at o: Int32) -> T { + let r = bb.read(def: T.self, position: Int(o + postion)) + return r + } +} diff --git a/src/FlatBuffers/Table.swift b/src/FlatBuffers/Table.swift new file mode 100644 index 000000000..0f783bfeb --- /dev/null +++ b/src/FlatBuffers/Table.swift @@ -0,0 +1,144 @@ +import Foundation + +public struct Table { + public private(set) var bb: ByteBuffer + public private(set) var postion: Int32 + + public init(bb: ByteBuffer, position: Int32 = 0) { + guard isLitteEndian else { + fatalError("Reading/Writing a buffer in big endian machine is not supported on swift") + } + self.bb = bb + self.postion = position + } + + public func offset(_ o: Int32) -> Int32 { + let vtable = postion - bb.read(def: Int32.self, position: Int(postion)) + return o < bb.read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read(def: Int16.self, position: Int(vtable + o))) : 0 + } + + public func indirect(_ o: Int32) -> Int32 { return o + bb.read(def: Int32.self, position: Int(o)) } + + /// String reads from the buffer with respect to position of the current table. + /// - Parameter offset: Offset of the string + public func string(at offset: Int32) -> String? { + return directString(at: offset + postion) + } + + /// Direct string reads from the buffer disregarding the position of the table. + /// It would be preferable to use string unless the current position of the table is not needed + /// - Parameter offset: Offset of the string + public func directString(at offset: Int32) -> String? { + var offset = offset + offset += bb.read(def: Int32.self, position: Int(offset)) + let count = bb.read(def: Int32.self, position: Int(offset)) + let position = offset + Int32(MemoryLayout.size) + return bb.readString(at: position, count: count) + } + + /// Reads from the buffer with respect to the position in the table. + /// - Parameters: + /// - type: Type of Scalar that needs to be read from the buffer + /// - o: Offset of the Element + public func readBuffer(of type: T.Type, at o: Int32) -> T { + return directRead(of: T.self, offset: o + postion) + } + + /// Reads from the buffer disregarding the position of the table. + /// It would be used when reading from an + /// ``` + /// let offset = __t.offset(10) + /// //Only used when the we already know what is the + /// // position in the table since __t.vector(at:) + /// // returns the index with respect to the position + /// __t.directRead(of: Byte.self, + /// offset: __t.vector(at: offset) + index * 1) + /// ``` + /// - Parameters: + /// - type: Type of Scalar that needs to be read from the buffer + /// - o: Offset of the Element + public func directRead(of type: T.Type, offset o: Int32) -> T { + let r = bb.read(def: T.self, position: Int(o)) + return r + } + + public func union(_ o: Int32) -> T { + let o = o + postion + return directUnion(o) + } + + public func directUnion(_ o: Int32) -> T { + return T.init(bb, o: o + bb.read(def: Int32.self, position: Int(o))) + } + + public func getVector(at off: Int32) -> [T]? { + let o = offset(off) + guard o != 0 else { return nil } + return bb.readSlice(index: vector(at: o), count: vector(count: o)) + } + + /// Vector count gets the count of Elements within the array + /// - Parameter o: start offset of the vector + /// - returns: Count of elements + public func vector(count o: Int32) -> Int32 { + var o = o + o += postion + o += bb.read(def: Int32.self, position: Int(o)) + return bb.read(def: Int32.self, position: Int(o)) + } + + /// Vector start index in the buffer + /// - Parameter o:start offset of the vector + /// - returns: the start index of the vector + public func vector(at o: Int32) -> Int32 { + var o = o + o += postion + return o + bb.read(def: Int32.self, position: Int(o)) + 4 + } +} + +extension Table { + + static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 { return o + fbb.read(def: Int32.self, position: Int(o)) } + + static public func offset(_ o: Int32, vOffset: Int32, fbb: ByteBuffer) -> Int32 { + let vTable = Int32(fbb.capacity) - o + return vTable + Int32(fbb.read(def: Int16.self, position: Int(vTable + vOffset - fbb.read(def: Int32.self, position: Int(vTable))))) + } + + static public func compare(_ off1: Int32, _ off2: Int32, fbb: ByteBuffer) -> Int32 { + let memorySize = Int32(MemoryLayout.size) + let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1)) + let _off2 = off2 + fbb.read(def: Int32.self, position: Int(off2)) + let len1 = fbb.read(def: Int32.self, position: Int(_off1)) + let len2 = fbb.read(def: Int32.self, position: Int(_off2)) + let startPos1 = _off1 + memorySize + let startPos2 = _off2 + memorySize + let minValue = min(len1, len2) + for i in 0...minValue { + let b1 = fbb.read(def: Int8.self, position: Int(i + startPos1)) + let b2 = fbb.read(def: Int8.self, position: Int(i + startPos2)) + if b1 != b2 { + return Int32(b2 - b1) + } + } + return len1 - len2 + } + + static public func compare(_ off1: Int32, _ key: [Byte], fbb: ByteBuffer) -> Int32 { + let memorySize = Int32(MemoryLayout.size) + let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1)) + let len1 = fbb.read(def: Int32.self, position: Int(_off1)) + let len2 = Int32(key.count) + let startPos1 = _off1 + memorySize + let minValue = min(len1, len2) + for i in 0..: Expr where L.ResultType == R.ResultType, L.ResultType: AdditiveArithmetic { + public typealias ResultType = L.ResultType + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result + rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func + (left: L, right: R) -> AdditionExpr where L.ResultType == R.ResultType, L.ResultType: AdditiveArithmetic { + return AdditionExpr(left: left, right: right) +} + +public func + (left: L, right: R) -> AdditionExpr> where L.ResultType == R, R: AdditiveArithmetic { + return AdditionExpr(left: left, right: ValueExpr(right)) +} + +public func + (left: L, right: R) -> AdditionExpr, R> where L: AdditiveArithmetic, L == R.ResultType { + return AdditionExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/And.swift b/src/exprs/And.swift new file mode 100644 index 000000000..9aea6c3e8 --- /dev/null +++ b/src/exprs/And.swift @@ -0,0 +1,62 @@ +import FlatBuffers + +public struct AndExpr: Expr where L.ResultType == R.ResultType, L.ResultType == Bool { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + // If any of these result is false and !unknown, the whole expression evaluated to false and !unknown + if ((!lval.result && !lval.unknown) || (!rval.result && !rval.unknown)) { + return (lval.result && rval.result, lval.unknown && rval.unknown) + } else { + return (lval.result && rval.result, lval.unknown || rval.unknown) + } + } + /* + * All expressions except And would return either .full or .none for index usefulness. Thus, if a field + * is not fully indexed, we won't generate indexed query. This is because it will result invalid result. + * For example, for a query (field1 = 1 OR field2 = 2), if field2 is removed because no index available, + * it will wrongfully exclude results that field1 != 1 but field2 = 2 (because the OR operator). Later + * scan operation won't recover that information because we started from a reduced dataset. + * + * IndexUsefulness reconcile these cases. For most binary / unary operators, if IndexUsefulness is not .full, + * it is .none, meaning we cannot reliably rely on partial index. For above OR operator, because field1 has + * .full while field2 has .none, it will return .none, and will effectively do a full-scan. However, partial + * index is still useful for a very limited case, such as below. For (field1 = 1 AND field2 = 2), because AND + * operator, using field1 index can still be helpful and we can start scan from a reduced dataset. It will be + * more efficient. + * + * So far, we haven't touched why it is 3-value. It seems we can just have two value: .full and .none, for AND + * operator, we can simply return .full if either of them returns .full. The gotcha comes from the combination. + * Considering (NOT (field1 = 1 AND field2 = 2)), in this case, we cannot only use index for field1 because + * (NOT field1 = 1) will give us a wrong reduced dataset, for cases (field1 = 1 AND field2 != 2), we wrongly + * excluded them from the reduced dataset. For this case, we need to have the 3rd state: .partial. Any other + * operator will binarize .partial into .none. So a NOT operator over AND of .partial state will return .none, + * and we won't apply the index for field1. All is good. + */ + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + let lval = left.canUsePartialIndex(availableIndexes) + let rval = right.canUsePartialIndex(availableIndexes) + if lval == .full && rval == .full { + return .full + } else if lval != .none || rval != .none { + return .partial + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func && (left: L, right: R) -> AndExpr where L.ResultType == R.ResultType, L.ResultType == Bool { + return AndExpr(left: left, right: right) +} + +public func && (left: L, right: Bool) -> AndExpr> where L.ResultType == Bool { + return AndExpr(left: left, right: ValueExpr(right)) +} + +public func && (left: Bool, right: R) -> AndExpr, R> where R.ResultType == Bool { + return AndExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/EqualTo.swift b/src/exprs/EqualTo.swift new file mode 100644 index 000000000..0a678b53f --- /dev/null +++ b/src/exprs/EqualTo.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct EqualToExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Equatable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result == rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func == (left: L, right: R) -> EqualToExpr where L.ResultType == R.ResultType, L.ResultType: Equatable { + return EqualToExpr(left: left, right: right) +} + +public func == (left: L, right: R) -> EqualToExpr> where L.ResultType == R, R: Equatable { + return EqualToExpr(left: left, right: ValueExpr(right)) +} + +public func == (left: L, right: R) -> EqualToExpr, R> where L: Equatable, L == R.ResultType { + return EqualToExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/Field.swift b/src/exprs/Field.swift new file mode 100644 index 000000000..7e69f5fa5 --- /dev/null +++ b/src/exprs/Field.swift @@ -0,0 +1,69 @@ +import FlatBuffers + +public struct OrderByField: OrderBy where T: DflatFriendlyValue { + let column: FieldExpr + public var name: String { column.name } + public let sortingOrder: SortingOrder + // See: https://www.sqlite.org/lang_select.html#orderby + // In short, SQLite considers Unknown (NULL) to be smaller than any value. This simply implement that behavior. + public func areInIncreasingOrder(_ lhs: FlatBufferObject, _ rhs: FlatBufferObject) -> Bool { + let lval = column.tableReader(lhs) + let rval = column.tableReader(rhs) + guard !lval.unknown || !rval.unknown else { return true } + if lval.unknown && !rval.unknown { + return true + } else if !lval.unknown && rval.unknown { + return false + } + return lval.result < rval.result + } + public func areInIncreasingOrder(_ lhs: DflatAtom, _ rhs: DflatAtom) -> Bool { + let lval = column.objectReader(lhs) + let rval = column.objectReader(rhs) + guard !lval.unknown || !rval.unknown else { return true } + if lval.unknown && !rval.unknown { + return true + } else if !lval.unknown && rval.unknown { + return false + } + return lval.result < rval.result + } +} + +public final class FieldExpr: Expr where T: DflatFriendlyValue { + public typealias ResultType = T + public typealias TableReader = (_ table: FlatBufferObject) -> (result: T, unknown: Bool) + public typealias ObjectReader = (_ object: DflatAtom) -> (result: T, unknown: Bool) + public let name: String + let tableReader: TableReader + let objectReader: ObjectReader + let primaryKey: Bool + let hasIndex: Bool + public required init(name: String, primaryKey: Bool, hasIndex: Bool, tableReader: @escaping TableReader, objectReader: @escaping ObjectReader) { + self.name = name + self.primaryKey = primaryKey + self.hasIndex = hasIndex + self.tableReader = tableReader + self.objectReader = objectReader + } + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + precondition(table != nil || object != nil) + if let table = table { + return tableReader(table) + } else if let object = object { + return objectReader(object) + } + fatalError() + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if primaryKey { + return .full + } + if hasIndex { + return availableIndexes.contains(name) ? .full : .none + } + return .none + } + public var useScanToRefine: Bool { !self.primaryKey && !self.hasIndex } + public var ascending: OrderByField { OrderByField(column: self, sortingOrder: .ascending) } + public var descending: OrderByField { OrderByField(column: self, sortingOrder: .descending) }} diff --git a/src/exprs/GreaterThan.swift b/src/exprs/GreaterThan.swift new file mode 100644 index 000000000..36c4ac36d --- /dev/null +++ b/src/exprs/GreaterThan.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct GreaterThanExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Comparable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result > rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func > (left: L, right: R) -> GreaterThanExpr where L.ResultType == R.ResultType, L.ResultType: Comparable { + return GreaterThanExpr(left: left, right: right) +} + +public func > (left: L, right: R) -> GreaterThanExpr> where L.ResultType == R, R: Comparable { + return GreaterThanExpr(left: left, right: ValueExpr(right)) +} + +public func > (left: L, right: R) -> GreaterThanExpr, R> where L: Comparable, L == R.ResultType { + return GreaterThanExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/GreaterThanOrEqualTo.swift b/src/exprs/GreaterThanOrEqualTo.swift new file mode 100644 index 000000000..4e8f464f5 --- /dev/null +++ b/src/exprs/GreaterThanOrEqualTo.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct GreaterThanOrEqualToExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Comparable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result >= rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func >= (left: L, right: R) -> GreaterThanOrEqualToExpr where L.ResultType == R.ResultType, L.ResultType: Comparable { + return GreaterThanOrEqualToExpr(left: left, right: right) +} + +public func >= (left: L, right: R) -> GreaterThanOrEqualToExpr> where L.ResultType == R, R: Comparable { + return GreaterThanOrEqualToExpr(left: left, right: ValueExpr(right)) +} + +public func >= (left: L, right: R) -> GreaterThanOrEqualToExpr, R> where L: Comparable, L == R.ResultType { + return GreaterThanOrEqualToExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/In.swift b/src/exprs/In.swift new file mode 100644 index 000000000..03dd53dbf --- /dev/null +++ b/src/exprs/In.swift @@ -0,0 +1,22 @@ +import FlatBuffers + +public struct InExpr: Expr where T.ResultType: Hashable, T.ResultType: DflatFriendlyValue { + public typealias ResultType = Bool + public let unary: T + public let set: Set + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let val = unary.evaluate(table: table, object: object) + guard (!val.unknown) else { return (false, true) } + return (set.contains(val.result), false) + } + public var useScanToRefine: Bool { unary.useScanToRefine } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + unary.canUsePartialIndex(availableIndexes) == .full ? .full : .none + } +} + +public extension Expr { + func `in`(_ sequence: S) -> InExpr where S: Sequence, S.Element == Self.ResultType { + InExpr(unary: self, set: Set(sequence)) + } +} diff --git a/src/exprs/IsNotNull.swift b/src/exprs/IsNotNull.swift new file mode 100644 index 000000000..4623076f2 --- /dev/null +++ b/src/exprs/IsNotNull.swift @@ -0,0 +1,20 @@ +import FlatBuffers + +public struct IsNotNullExpr: Expr { + public typealias ResultType = Bool + public let unary: T + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let val = unary.evaluate(table: table, object: object) + return (!val.unknown, false) + } + public var useScanToRefine: Bool { unary.useScanToRefine } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + unary.canUsePartialIndex(availableIndexes) == .full ? .full : .none + } +} + +public extension Expr { + func isNotNull() -> IsNotNullExpr { + IsNotNullExpr(unary: self) + } +} diff --git a/src/exprs/IsNull.swift b/src/exprs/IsNull.swift new file mode 100644 index 000000000..66ce458e6 --- /dev/null +++ b/src/exprs/IsNull.swift @@ -0,0 +1,20 @@ +import FlatBuffers + +public struct IsNullExpr: Expr { + public typealias ResultType = Bool + public let unary: T + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let val = unary.evaluate(table: table, object: object) + return (val.unknown, false) + } + public var useScanToRefine: Bool { unary.useScanToRefine } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + unary.canUsePartialIndex(availableIndexes) == .full ? .full : .none + } +} + +public extension Expr { + func isNull() -> IsNullExpr { + IsNullExpr(unary: self) + } +} diff --git a/src/exprs/LessThan.swift b/src/exprs/LessThan.swift new file mode 100644 index 000000000..57f27fa2d --- /dev/null +++ b/src/exprs/LessThan.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct LessThanExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Comparable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result < rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func < (left: L, right: R) -> LessThanExpr where L.ResultType == R.ResultType, L.ResultType: Comparable { + return LessThanExpr(left: left, right: right) +} + +public func < (left: L, right: R) -> LessThanExpr> where L.ResultType == R, R: Comparable { + return LessThanExpr(left: left, right: ValueExpr(right)) +} + +public func < (left: L, right: R) -> LessThanExpr, R> where L: Comparable, L == R.ResultType { + return LessThanExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/LessThanOrEqualTo.swift b/src/exprs/LessThanOrEqualTo.swift new file mode 100644 index 000000000..37825d80d --- /dev/null +++ b/src/exprs/LessThanOrEqualTo.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct LessThanOrEqualToExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Comparable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result <= rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func <= (left: L, right: R) -> LessThanOrEqualToExpr where L.ResultType == R.ResultType, L.ResultType: Comparable { + return LessThanOrEqualToExpr(left: left, right: right) +} + +public func <= (left: L, right: R) -> LessThanOrEqualToExpr> where L.ResultType == R, R: Comparable { + return LessThanOrEqualToExpr(left: left, right: ValueExpr(right)) +} + +public func <= (left: L, right: R) -> LessThanOrEqualToExpr, R> where L: Comparable, L == R.ResultType { + return LessThanOrEqualToExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/Mod.swift b/src/exprs/Mod.swift new file mode 100644 index 000000000..c7d84168d --- /dev/null +++ b/src/exprs/Mod.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct ModExpr: Expr where L.ResultType == R.ResultType, L.ResultType: BinaryInteger { + public typealias ResultType = L.ResultType + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result % rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func % (left: L, right: R) -> ModExpr where L.ResultType == R.ResultType, L.ResultType: BinaryInteger { + return ModExpr(left: left, right: right) +} + +public func % (left: L, right: R) -> ModExpr> where L.ResultType == R, L.ResultType: BinaryInteger { + return ModExpr(left: left, right: ValueExpr(right)) +} + +public func % (left: L, right: R) -> ModExpr, R> where L: BinaryInteger, L == R.ResultType { + return ModExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/Not.swift b/src/exprs/Not.swift new file mode 100644 index 000000000..2e857fe3c --- /dev/null +++ b/src/exprs/Not.swift @@ -0,0 +1,18 @@ +import FlatBuffers + +public struct NotExpr: Expr where T.ResultType == Bool { + public typealias ResultType = Bool + public let unary: T + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let val = unary.evaluate(table: table, object: object) + return (!val.result, val.unknown) + } + public var useScanToRefine: Bool { unary.useScanToRefine } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + unary.canUsePartialIndex(availableIndexes) == .full ? .full : .none + } +} + +public prefix func ! (unary: T) -> NotExpr where T.ResultType == Bool { + return NotExpr(unary: unary) +} diff --git a/src/exprs/NotEqualTo.swift b/src/exprs/NotEqualTo.swift new file mode 100644 index 000000000..017f3eebf --- /dev/null +++ b/src/exprs/NotEqualTo.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct NotEqualToExpr: Expr where L.ResultType == R.ResultType, L.ResultType: Equatable { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result != rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func != (left: L, right: R) -> NotEqualToExpr where L.ResultType == R.ResultType, L.ResultType: Equatable { + return NotEqualToExpr(left: left, right: right) +} + +public func != (left: L, right: R) -> NotEqualToExpr> where L.ResultType == R, R: Equatable { + return NotEqualToExpr(left: left, right: ValueExpr(right)) +} + +public func != (left: L, right: R) -> NotEqualToExpr, R> where L: Equatable, L == R.ResultType { + return NotEqualToExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/NotIn.swift b/src/exprs/NotIn.swift new file mode 100644 index 000000000..3ca2c6746 --- /dev/null +++ b/src/exprs/NotIn.swift @@ -0,0 +1,22 @@ +import FlatBuffers + +public struct NotInExpr: Expr where T.ResultType: Hashable, T.ResultType: DflatFriendlyValue { + public typealias ResultType = Bool + public let unary: T + public let set: Set + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let val = unary.evaluate(table: table, object: object) + guard (!val.unknown) else { return (false, true) } + return (!set.contains(val.result), false) + } + public var useScanToRefine: Bool { unary.useScanToRefine } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + unary.canUsePartialIndex(availableIndexes) == .full ? .full : .none + } +} + +public extension Expr { + func notIn(_ sequence: S) -> NotInExpr where S: Sequence, S.Element == Self.ResultType { + NotInExpr(unary: self, set: Set(sequence)) + } +} diff --git a/src/exprs/Or.swift b/src/exprs/Or.swift new file mode 100644 index 000000000..77eafc036 --- /dev/null +++ b/src/exprs/Or.swift @@ -0,0 +1,36 @@ +import FlatBuffers + +public struct OrExpr: Expr where L.ResultType == R.ResultType, L.ResultType == Bool { + public typealias ResultType = Bool + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + // If any of these result is true and !unknown, the whole expression evaluated to true and !unknown + if ((lval.result && !lval.unknown) || (rval.result && !rval.unknown)) { + return (lval.result || rval.result, lval.unknown && rval.unknown) + } else { + return (lval.result || rval.result, lval.unknown || rval.unknown) + } + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func || (left: L, right: R) -> OrExpr where L.ResultType == R.ResultType, L.ResultType == Bool { + return OrExpr(left: left, right: right) +} + +public func || (left: L, right: Bool) -> OrExpr> where L.ResultType == Bool { + return OrExpr(left: left, right: ValueExpr(right)) +} + +public func || (left: Bool, right: R) -> OrExpr, R> where R.ResultType == Bool { + return OrExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/Subtraction.swift b/src/exprs/Subtraction.swift new file mode 100644 index 000000000..36b171d6b --- /dev/null +++ b/src/exprs/Subtraction.swift @@ -0,0 +1,31 @@ +import FlatBuffers + +public struct SubtractionExpr: Expr where L.ResultType == R.ResultType, L.ResultType: AdditiveArithmetic { + public typealias ResultType = L.ResultType + public let left: L + public let right: R + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + let lval = left.evaluate(table: table, object: object) + let rval = right.evaluate(table: table, object: object) + return (lval.result - rval.result, lval.unknown || rval.unknown) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + if left.canUsePartialIndex(availableIndexes) == .full && right.canUsePartialIndex(availableIndexes) == .full { + return .full + } + return .none + } + public var useScanToRefine: Bool { left.useScanToRefine || right.useScanToRefine } +} + +public func - (left: L, right: R) -> SubtractionExpr where L.ResultType == R.ResultType, L.ResultType: AdditiveArithmetic { + return SubtractionExpr(left: left, right: right) +} + +public func - (left: L, right: R) -> SubtractionExpr> where L.ResultType == R, R: AdditiveArithmetic { + return SubtractionExpr(left: left, right: ValueExpr(right)) +} + +public func - (left: L, right: R) -> SubtractionExpr, R> where L: AdditiveArithmetic, L == R.ResultType { + return SubtractionExpr(left: ValueExpr(left), right: right) +} diff --git a/src/exprs/Value.swift b/src/exprs/Value.swift new file mode 100644 index 000000000..859885504 --- /dev/null +++ b/src/exprs/Value.swift @@ -0,0 +1,37 @@ +import FlatBuffers + +public protocol DflatFriendlyValue: Comparable {} + +extension Bool: Comparable { + public static func < (lhs: Bool, rhs: Bool) -> Bool { + return lhs == false + } +} + +extension Bool: DflatFriendlyValue {} +extension Int8: DflatFriendlyValue {} +extension UInt8: DflatFriendlyValue {} +extension Int16: DflatFriendlyValue {} +extension UInt16: DflatFriendlyValue {} +extension Int32: DflatFriendlyValue {} +extension UInt32: DflatFriendlyValue {} +extension Int64: DflatFriendlyValue {} +extension UInt64: DflatFriendlyValue {} +extension Float: DflatFriendlyValue {} +extension Double: DflatFriendlyValue {} +extension String: DflatFriendlyValue {} + +public struct ValueExpr: Expr where T: DflatFriendlyValue { + public typealias ResultType = T + public let value: T + internal init(_ value: T) { + self.value = value + } + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + (value, false) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + .full + } + public var useScanToRefine: Bool { false } +} diff --git a/src/sqlite/SQLiteConnection.swift b/src/sqlite/SQLiteConnection.swift new file mode 100644 index 000000000..71840791f --- /dev/null +++ b/src/sqlite/SQLiteConnection.swift @@ -0,0 +1,56 @@ +import SQLite3 + +final class SQLiteConnection { + var sqlite: OpaquePointer? + private var stringPool = [String: OpaquePointer]() + private var staticPool = [UnsafePointer: OpaquePointer]() + init?(filePath: String) { + guard SQLITE_OK == sqlite3_open_v2(filePath, &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nil) else { return nil } + guard sqlite != nil else { return nil } + } + deinit { + guard let sqlite = sqlite else { return } + for prepared in stringPool.values { + sqlite3_finalize(prepared) + } + for prepared in staticPool.values { + sqlite3_finalize(prepared) + } + sqlite3_close(sqlite) + } + func close() { + guard let sqlite = sqlite else { return } + for prepared in stringPool.values { + sqlite3_finalize(prepared) + } + for prepared in staticPool.values { + sqlite3_finalize(prepared) + } + sqlite3_close(sqlite) + } + func prepareStatement(_ statement: String) -> OpaquePointer? { + guard let sqlite = sqlite else { return nil } + if let prepared = stringPool[statement] { + return prepared + } + var prepared: OpaquePointer? = nil + sqlite3_prepare_v2(sqlite, statement, -1, &prepared, nil) + if let prepared = prepared { + stringPool[statement] = prepared + } + return prepared + } + func prepareStatement(_ statement: StaticString) -> OpaquePointer? { + guard let sqlite = sqlite else { return nil } + let identifier = statement.utf8Start + if let prepared = staticPool[identifier] { + return prepared + } + var prepared: OpaquePointer? = nil + sqlite3_prepare_v2(sqlite, UnsafeRawPointer(identifier).assumingMemoryBound(to: Int8.self), -1, &prepared, nil) + if let prepared = prepared { + staticPool[identifier] = prepared + } + return prepared + } +} diff --git a/src/sqlite/SQLiteConnectionPool.swift b/src/sqlite/SQLiteConnectionPool.swift new file mode 100644 index 000000000..2b305cdc8 --- /dev/null +++ b/src/sqlite/SQLiteConnectionPool.swift @@ -0,0 +1,58 @@ +import Dispatch +import SQLite3 + +final class SQLiteConnectionPool { + + final class Borrowed { + public let pointee: SQLiteConnection? + private let pool: SQLiteConnectionPool? + init(_ pointee: SQLiteConnection?, _ pool: SQLiteConnectionPool?) { + self.pointee = pointee + self.pool = pool + } + public init(_ pointee: SQLiteConnection?) { + self.pointee = pointee + self.pool = nil + } + deinit { + guard let pointee = pointee else { return } + pool?.add(pointee) + } + } + + private var pool = [SQLiteConnection]() + private let filePath: String + private let flowControl: DispatchSemaphore + private let lock: os_unfair_lock_t + init(capacity: Int, filePath: String) { + self.filePath = filePath + flowControl = DispatchSemaphore(value: capacity) + lock = os_unfair_lock_t.allocate(capacity: 1) + lock.initialize(to: os_unfair_lock()) + } + deinit { + lock.deallocate() + } + func borrow() -> Borrowed { + flowControl.wait() + os_unfair_lock_lock(lock) + if let connection = pool.last { + pool.removeLast() + os_unfair_lock_unlock(lock) + return Borrowed(connection, self) + } + os_unfair_lock_unlock(lock) + let pointee = SQLiteConnection(filePath: filePath) + if pointee == nil { + flowControl.signal() + } + sqlite3_busy_timeout(pointee?.sqlite, 10_000) + return Borrowed(pointee, self) + } + fileprivate func add(_ connection: SQLiteConnection) { + os_unfair_lock_lock(lock) + pool.append(connection) + os_unfair_lock_unlock(lock) + flowControl.signal() + } +} diff --git a/src/sqlite/SQLiteDflat.swift b/src/sqlite/SQLiteDflat.swift new file mode 100644 index 000000000..98aa75371 --- /dev/null +++ b/src/sqlite/SQLiteDflat.swift @@ -0,0 +1,76 @@ +import Dflat +import SQLite3 +import Dispatch + +public final class SQLiteDflat: Dflat { + + public enum FileProtectionLevel: Int32 { + case noProtection = 4 // Class D + case completeFileProtection = 1 // Class A + case completeFileProtectionUnlessOpen = 2 // Class B + case completeFileProtectionUntilFirstUserAuthentication = 3 // Class C + } + private let filePath: String + private let fileProtectionLevel: FileProtectionLevel + private let queue: DispatchQueue + private var writer: SQLiteConnection? + private let readerPool: SQLiteConnectionPool + + public required init(filePath: String, fileProtectionLevel: FileProtectionLevel, queue: DispatchQueue = DispatchQueue(label: "com.dflat.write", qos: .utility)) { + self.filePath = filePath + self.fileProtectionLevel = fileProtectionLevel + self.queue = queue + self.readerPool = SQLiteConnectionPool(capacity: 64, filePath: filePath) + queue.async { [weak self] in + self?.prepareData() + } + } + + public func performChanges(_ anyPool: [Any.Type], changesHandler: @escaping Dflat.ChangesHandler, completionHandler: Dflat.CompletionHandler? = nil) { + queue.async { [weak self] in + self?.invokeChangesHandler(anyPool, changesHandler: changesHandler, completionHandler: completionHandler) + } + } + + public func fetchFor(ofType: T.Type) -> DflatQueryBuilder { + let reader = readerPool.borrow() + return SQLiteDflatQueryBuilder(reader) + } + + static func setUpFilePathWithProtectionLevel(filePath: String, fileProtectionLevel: FileProtectionLevel) { + #if !targetEnvironment(simulator) + let fd = open_dprotected_np(filePath, O_CREAT | O_WRONLY, fileProtectionLevel.rawValue, 0, 0666) + close(fd) + let wal = open_dprotected_np(filePath + "-wal", O_CREAT | O_WRONLY, fileProtectionLevel.rawValue, 0, 0666) + close(wal) + let shm = open_dprotected_np(filePath + "-shm", O_CREAT | O_WRONLY, fileProtectionLevel.rawValue, 0, 0666) + close(shm) + #endif + } + + private func prepareData() { + dispatchPrecondition(condition: .onQueue(queue)) + // Set the flag before creating the s + SQLiteDflat.setUpFilePathWithProtectionLevel(filePath: filePath, fileProtectionLevel: fileProtectionLevel) + writer = SQLiteConnection(filePath: filePath) + guard let writer = writer else { return } + sqlite3_busy_timeout(writer.sqlite, 10_000) + sqlite3_exec(writer.sqlite, "PRAGMA journal_mode=WAL", nil, nil, nil) + sqlite3_exec(writer.sqlite, "PRAGMA auto_vacuum=incremental", nil, nil, nil) + sqlite3_exec(writer.sqlite, "PRAGMA incremental_vaccum(2)", nil, nil, nil) + } + + private func invokeChangesHandler(_ anyPool: [Any.Type], changesHandler: Dflat.ChangesHandler, completionHandler: Dflat.CompletionHandler?) { + guard let writer = writer else { + completionHandler?(false) + return + } + let txContext = SQLiteDflatTransactionContext() + let begin = writer.prepareStatement("BEGIN") + sqlite3_step(begin) + changesHandler(txContext) + let commit = writer.prepareStatement("COMMIT") + sqlite3_step(commit) + completionHandler?(false) + } +} diff --git a/src/sqlite/SQLiteDflatAtom.swift b/src/sqlite/SQLiteDflatAtom.swift new file mode 100644 index 000000000..099e4079e --- /dev/null +++ b/src/sqlite/SQLiteDflatAtom.swift @@ -0,0 +1,4 @@ +public protocol SQLiteDflatAtom { + var table: String { get } + var indexFields: [String] { get } +} diff --git a/src/sqlite/SQLiteDflatFetchedResult.swift b/src/sqlite/SQLiteDflatFetchedResult.swift new file mode 100644 index 000000000..6b98b54cb --- /dev/null +++ b/src/sqlite/SQLiteDflatFetchedResult.swift @@ -0,0 +1,15 @@ +import Dflat + +final class SQLiteDflatFetchedResult: DflatFetchedResult { + private let clause: AnySQLiteExpr + private let limit: Limit + private let orderBy: [OrderBy] + + init(_ array: [Element], clause: AnySQLiteExpr, limit: Limit, orderBy: [OrderBy]) { + self.clause = clause + self.limit = limit + self.orderBy = orderBy + super.init(array) + } + +} diff --git a/src/sqlite/SQLiteDflatQueryBuilder.swift b/src/sqlite/SQLiteDflatQueryBuilder.swift new file mode 100644 index 000000000..304eb2564 --- /dev/null +++ b/src/sqlite/SQLiteDflatQueryBuilder.swift @@ -0,0 +1,23 @@ +import Dflat + +func SQLiteQueryWhere(reader: SQLiteConnectionPool.Borrowed, clause: AnySQLiteExpr, limit: Limit, orderBy: [OrderBy], result: inout [Element]) { + var statement = "" + var parameterCount: Int32 = 0 + clause.buildWhereClause(availableIndexes: Set(), clause: &statement, parameterCount: ¶meterCount) + parameterCount = 0 + // clause.bindWhereClause(availableIndexes: Set(), clause: preparedQuery, parameterCount: ¶meterCount) +} + +final class SQLiteDflatQueryBuilder: DflatQueryBuilder { + private let reader: SQLiteConnectionPool.Borrowed + public init(_ reader: SQLiteConnectionPool.Borrowed) { + self.reader = reader + super.init() + } + override func `where`(_ clause: T, limit: Limit = .noLimit, orderBy: [OrderBy] = []) -> DflatFetchedResult where T.ResultType == Bool { + let sqlClause = AnySQLiteExpr(clause, clause as! SQLiteExpr) + var result = [Element]() + SQLiteQueryWhere(reader: reader, clause: sqlClause, limit: limit, orderBy: orderBy, result: &result) + return SQLiteDflatFetchedResult(result, clause: sqlClause, limit: limit, orderBy: orderBy) + } +} diff --git a/src/sqlite/SQLiteDflatTransactionContext.swift b/src/sqlite/SQLiteDflatTransactionContext.swift new file mode 100644 index 000000000..73df89af4 --- /dev/null +++ b/src/sqlite/SQLiteDflatTransactionContext.swift @@ -0,0 +1,8 @@ +import Dflat + +struct SQLiteDflatTransactionContext: DflatTransactionContext { + private let writer: SQLiteConnection + func submit(_: DflatChangeRequest) -> Bool { + return true + } +} diff --git a/src/sqlite/SQLiteExpr.swift b/src/sqlite/SQLiteExpr.swift new file mode 100644 index 000000000..565a5b195 --- /dev/null +++ b/src/sqlite/SQLiteExpr.swift @@ -0,0 +1,59 @@ +import Dflat +import FlatBuffers + +public protocol SQLiteExpr { + func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) + func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) +} + +private class _AnyExprBase: Expr { + func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + fatalError() + } + func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + fatalError() + } + var useScanToRefine: Bool { fatalError() } +} + +private class _AnyExpr: _AnyExprBase { + private let base: T + init(_ base: T) { + self.base = base + } + override func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + base.evaluate(table: table, object: object) + } + override func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + base.canUsePartialIndex(availableIndexes) + } + override var useScanToRefine: Bool { base.useScanToRefine } +} + +public final class AnySQLiteExpr: Expr, SQLiteExpr { + private let sqlBase: SQLiteExpr + private let base: _AnyExprBase + public init(_ base: T) where T.ResultType == ResultType, T: SQLiteExpr { + self.sqlBase = base + self.base = _AnyExpr(base) + } + // This is the weird bit, since we have to force cast to SQLiteExpr, hence, we cannot really + // Put them into one parameter. This has to be two. + public init(_ base: T, _ sqlBase: SQLiteExpr) where T.ResultType == ResultType { + self.sqlBase = sqlBase + self.base = _AnyExpr(base) + } + public func evaluate(table: FlatBufferObject?, object: DflatAtom?) -> (result: ResultType, unknown: Bool) { + base.evaluate(table: table, object: object) + } + public func canUsePartialIndex(_ availableIndexes: Set) -> IndexUsefulness { + base.canUsePartialIndex(availableIndexes) + } + public var useScanToRefine: Bool { base.useScanToRefine } + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + sqlBase.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + sqlBase.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteAddition.swift b/src/sqlite/exprs/SQLiteAddition.swift new file mode 100644 index 000000000..846c28f00 --- /dev/null +++ b/src/sqlite/exprs/SQLiteAddition.swift @@ -0,0 +1,17 @@ +import Dflat + +extension AdditionExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") + (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteAnd.swift b/src/sqlite/exprs/SQLiteAnd.swift new file mode 100644 index 000000000..ca768d917 --- /dev/null +++ b/src/sqlite/exprs/SQLiteAnd.swift @@ -0,0 +1,27 @@ +import Dflat + +extension AndExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + let lval = left.canUsePartialIndex(availableIndexes) + let rval = right.canUsePartialIndex(availableIndexes) + if lval != .none && rval != .none { + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") AND (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } else if lval != .none { + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + } else if rval != .none { + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + } + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + if left.canUsePartialIndex(availableIndexes) != .none { + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } + if right.canUsePartialIndex(availableIndexes) != .none { + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } + } +} diff --git a/src/sqlite/exprs/SQLiteEqualTo.swift b/src/sqlite/exprs/SQLiteEqualTo.swift new file mode 100644 index 000000000..0dd029d39 --- /dev/null +++ b/src/sqlite/exprs/SQLiteEqualTo.swift @@ -0,0 +1,17 @@ +import Dflat + +extension EqualToExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") = (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteField.swift b/src/sqlite/exprs/SQLiteField.swift new file mode 100644 index 000000000..2aee18cb9 --- /dev/null +++ b/src/sqlite/exprs/SQLiteField.swift @@ -0,0 +1,9 @@ +import Dflat + +extension FieldExpr: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append(self.name) + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) {} +} diff --git a/src/sqlite/exprs/SQLiteGreaterThan.swift b/src/sqlite/exprs/SQLiteGreaterThan.swift new file mode 100644 index 000000000..189be85a4 --- /dev/null +++ b/src/sqlite/exprs/SQLiteGreaterThan.swift @@ -0,0 +1,17 @@ +import Dflat + +extension GreaterThanExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") > (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteGreaterThanOrEqualTo.swift b/src/sqlite/exprs/SQLiteGreaterThanOrEqualTo.swift new file mode 100644 index 000000000..4c723cd3a --- /dev/null +++ b/src/sqlite/exprs/SQLiteGreaterThanOrEqualTo.swift @@ -0,0 +1,17 @@ +import Dflat + +extension GreaterThanOrEqualToExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") >= (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteIn.swift b/src/sqlite/exprs/SQLiteIn.swift new file mode 100644 index 000000000..a23fb8aea --- /dev/null +++ b/src/sqlite/exprs/SQLiteIn.swift @@ -0,0 +1,28 @@ +import Dflat + +extension InExpr: SQLiteExpr where T: SQLiteExpr, T.ResultType: SQLiteValue { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + unary.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") IN (") + let count = set.count + if count > 0 { + parameterCount += 1 + clause.append("?\(parameterCount)") + } + for _ in 1.., clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + unary.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + for i in set { + parameterCount += 1 + i.bindSQLite(clause, parameterId: parameterCount) + } + } +} diff --git a/src/sqlite/exprs/SQLiteIsNotNull.swift b/src/sqlite/exprs/SQLiteIsNotNull.swift new file mode 100644 index 000000000..1bc7ebf00 --- /dev/null +++ b/src/sqlite/exprs/SQLiteIsNotNull.swift @@ -0,0 +1,14 @@ +import Dflat + +extension IsNotNullExpr: SQLiteExpr where T: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + unary.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") IS NOT NULL") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + unary.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteIsNull.swift b/src/sqlite/exprs/SQLiteIsNull.swift new file mode 100644 index 000000000..eb987a147 --- /dev/null +++ b/src/sqlite/exprs/SQLiteIsNull.swift @@ -0,0 +1,14 @@ +import Dflat + +extension IsNullExpr: SQLiteExpr where T: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + unary.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") ISNULL") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + unary.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteLessThan.swift b/src/sqlite/exprs/SQLiteLessThan.swift new file mode 100644 index 000000000..ba30eb470 --- /dev/null +++ b/src/sqlite/exprs/SQLiteLessThan.swift @@ -0,0 +1,17 @@ +import Dflat + +extension LessThanExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") < (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteLessThanOrEqualTo.swift b/src/sqlite/exprs/SQLiteLessThanOrEqualTo.swift new file mode 100644 index 000000000..5243fca04 --- /dev/null +++ b/src/sqlite/exprs/SQLiteLessThanOrEqualTo.swift @@ -0,0 +1,17 @@ +import Dflat + +extension LessThanOrEqualToExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") <= (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteMod.swift b/src/sqlite/exprs/SQLiteMod.swift new file mode 100644 index 000000000..7e562de7e --- /dev/null +++ b/src/sqlite/exprs/SQLiteMod.swift @@ -0,0 +1,17 @@ +import Dflat + +extension ModExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") % (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteNot.swift b/src/sqlite/exprs/SQLiteNot.swift new file mode 100644 index 000000000..63a767cbb --- /dev/null +++ b/src/sqlite/exprs/SQLiteNot.swift @@ -0,0 +1,15 @@ +import Dflat + +extension NotExpr: SQLiteExpr where T: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("NOT (") + unary.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + unary.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + // TODO: + } +} diff --git a/src/sqlite/exprs/SQLiteNotEqualTo.swift b/src/sqlite/exprs/SQLiteNotEqualTo.swift new file mode 100644 index 000000000..e37708cef --- /dev/null +++ b/src/sqlite/exprs/SQLiteNotEqualTo.swift @@ -0,0 +1,17 @@ +import Dflat + +extension NotEqualToExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") != (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteNotIn.swift b/src/sqlite/exprs/SQLiteNotIn.swift new file mode 100644 index 000000000..0aed591d3 --- /dev/null +++ b/src/sqlite/exprs/SQLiteNotIn.swift @@ -0,0 +1,28 @@ +import Dflat + +extension NotInExpr: SQLiteExpr where T: SQLiteExpr, T.ResultType: SQLiteValue { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + unary.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") NOT IN (") + let count = set.count + if count > 0 { + parameterCount += 1 + clause.append("?\(parameterCount)") + } + for _ in 1.., clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + unary.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + for i in set { + parameterCount += 1 + i.bindSQLite(clause, parameterId: parameterCount) + } + } +} diff --git a/src/sqlite/exprs/SQLiteOr.swift b/src/sqlite/exprs/SQLiteOr.swift new file mode 100644 index 000000000..0798f407c --- /dev/null +++ b/src/sqlite/exprs/SQLiteOr.swift @@ -0,0 +1,17 @@ +import Dflat + +extension OrExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") OR (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteSubtraction.swift b/src/sqlite/exprs/SQLiteSubtraction.swift new file mode 100644 index 000000000..dc2d3c753 --- /dev/null +++ b/src/sqlite/exprs/SQLiteSubtraction.swift @@ -0,0 +1,17 @@ +import Dflat + +extension SubtractionExpr: SQLiteExpr where L: SQLiteExpr, R: SQLiteExpr { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + clause.append("(") + left.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(") - (") + right.buildWhereClause(availableIndexes: availableIndexes, clause: &clause, parameterCount: ¶meterCount) + clause.append(")") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + guard self.canUsePartialIndex(availableIndexes) == .full else { return } + left.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + right.bindWhereClause(availableIndexes: availableIndexes, clause: clause, parameterCount: ¶meterCount) + } +} diff --git a/src/sqlite/exprs/SQLiteValue.swift b/src/sqlite/exprs/SQLiteValue.swift new file mode 100644 index 000000000..cf17180da --- /dev/null +++ b/src/sqlite/exprs/SQLiteValue.swift @@ -0,0 +1,84 @@ +import Dflat +import SQLite3 + +public protocol SQLiteValue: DflatFriendlyValue { + func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) +} + +extension ValueExpr: SQLiteExpr where T: SQLiteValue { + public func buildWhereClause(availableIndexes: Set, clause: inout String, parameterCount: inout Int32) { + parameterCount += 1 + let parameterId = parameterCount + clause.append("?\(parameterId)") + } + public func bindWhereClause(availableIndexes: Set, clause: OpaquePointer, parameterCount: inout Int32) { + parameterCount += 1 + let parameterId = parameterCount + value.bindSQLite(clause, parameterId: parameterId) + } +} + +// MARK - Implement binding for SQLite. + +extension Bool: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, self ? 1 : 0) + } +} +extension Int8: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, Int32(self)) + } +} +extension UInt8: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, Int32(self)) + } +} +extension Int16: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, Int32(self)) + } +} +extension UInt16: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, Int32(self)) + } +} +extension Int32: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, self) + } +} +extension UInt32: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int(clause, parameterId, Int32(self)) + } +} +extension Int64: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int64(clause, parameterId, self) + } +} +extension UInt64: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_int64(clause, parameterId, Int64(self)) + } +} +extension Float: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_double(clause, parameterId, Double(self)) + } +} +extension Double: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + sqlite3_bind_double(clause, parameterId, self) + } +} +extension String: SQLiteValue { + public func bindSQLite(_ clause: OpaquePointer, parameterId: Int32) { + // This is not ideal, but there isn't a good way to guarentee life-cycle of the String from Swift. + let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self) + sqlite3_bind_text(clause, parameterId, self, -1, SQLITE_TRANSIENT) + } +} diff --git a/src/tests/BUILD b/src/tests/BUILD new file mode 100644 index 000000000..5b56de266 --- /dev/null +++ b/src/tests/BUILD @@ -0,0 +1,43 @@ +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_test") + +swift_library( + name = "ExprTests_lib", + srcs = [ + "ExprTests.swift", + ], + deps = [ + "//src:Dflat", + ], +) + +swift_library( + name = "SQLiteDflatTests_lib", + srcs = [ + "monster_generated.swift", + "Monster.swift", + "MonsterChangeRequest.swift", + "SQLiteDflatTests.swift", + ], + deps = [ + "//src:SQLiteDflat", + ], +) + +ios_unit_test( + name = "Tests_ios", + deps = [ + ":ExprTests_lib", + ":SQLiteDflatTests_lib", + ], + minimum_os_version = "10.0", +) + +swift_test( + name = "Tests", + deps = [ + ":ExprTests_lib", + ":SQLiteDflatTests_lib", + ], +) diff --git a/src/tests/ExprTests.swift b/src/tests/ExprTests.swift new file mode 100644 index 000000000..2ab8f858a --- /dev/null +++ b/src/tests/ExprTests.swift @@ -0,0 +1,176 @@ +import Dflat +import XCTest +import FlatBuffers + +final class TestObj: DflatAtom { + var x: Int32 = 0 + var y: Float = 0 +} + +func testObjXTable(_ table: FlatBufferObject) -> (result: Int32, unknown: Bool) { + return (0, true) +} + +func testObjX(_ object: DflatAtom) -> (result: Int32, unknown: Bool) { + let object: TestObj = object as! TestObj + return (object.x, false) +} + +func testObjX10AsNull(_ object: DflatAtom) -> (result: Int32, unknown: Bool) { + let object: TestObj = object as! TestObj + if object.x == 10 { + return (object.x, true) + } + return (object.x, false) +} + +func testObjYTable(_ table: FlatBufferObject) -> (result: Float, unknown: Bool) { + return (0, true) +} + +func testObjY(_ object: DflatAtom) -> (result: Float, unknown: Bool) { + let object: TestObj = object as! TestObj + return (object.y, false) +} + +class ExprTests: XCTestCase { + + func testEvaluateField() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX) + let columnY = FieldExpr(name: "y", primaryKey: false, hasIndex: false, tableReader: testObjYTable, objectReader: testObjY) + let testObj = TestObj() + testObj.x = 10 + let retval0 = columnX.evaluate(object: testObj) + XCTAssertEqual(10, retval0.result) + testObj.x = 12 + let retval1 = columnX.evaluate(object: testObj) + XCTAssertEqual(12, retval1.result) + testObj.y = 0.124 + let retval2 = columnY.evaluate(object: testObj) + XCTAssertEqual(0.124, retval2.result) + testObj.y = 0.24 + let retval3 = columnY.evaluate(object: testObj) + XCTAssertEqual(0.24, retval3.result) + } + + func testEvaluateEqualTo() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX) + let testObj = TestObj() + testObj.x = 10 + let retval0 = (columnX == 10).evaluate(object: testObj) + XCTAssertTrue(retval0.result) + XCTAssertFalse(retval0.unknown) + let retval1 = (columnX == 11).evaluate(object: testObj) + XCTAssertFalse(retval1.result) + XCTAssertFalse(retval1.unknown) + let retval2 = (columnX != 10).evaluate(object: testObj) + XCTAssertFalse(retval2.result) + XCTAssertFalse(retval2.unknown) + let retval3 = (columnX != 11).evaluate(object: testObj) + XCTAssertTrue(retval3.result) + XCTAssertFalse(retval3.unknown) + } + + func testBuildComplexExpression() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX) + let testObj = TestObj() + testObj.x = 10 + let retval0 = (columnX == 10).evaluate(object: testObj) + XCTAssertTrue(retval0.result) + XCTAssertFalse(retval0.unknown) + let retval1 = (columnX == 11).evaluate(object: testObj) + XCTAssertFalse(retval1.result) + XCTAssertFalse(retval1.unknown) + let retval2 = (columnX > 9).evaluate(object: testObj) + XCTAssertTrue(retval2.result) + XCTAssertFalse(retval2.unknown) + let andCond0 = ((columnX == 10) && (columnX == 11)) + let retval3 = andCond0.evaluate(object: testObj) + XCTAssertFalse(retval3.result) + XCTAssertFalse(retval3.unknown) + let andCond1 = ((columnX == 10) && (columnX > 9)) + let retval4 = andCond1.evaluate(object: testObj) + XCTAssertTrue(retval4.result) + XCTAssertFalse(retval4.unknown) + let orCond0 = (andCond0 || andCond1) + let retval5 = orCond0.evaluate(object: testObj) + XCTAssertTrue(retval5.result) + XCTAssertFalse(retval5.unknown) + let retval6 = (!orCond0).evaluate(object: testObj) + XCTAssertFalse(retval6.result) + XCTAssertFalse(retval6.unknown) + } + + func testArithmetic() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX) + let columnY = FieldExpr(name: "y", primaryKey: false, hasIndex: false, tableReader: testObjYTable, objectReader: testObjY) + let testObj = TestObj() + testObj.x = 10 + testObj.y = 1.0 + let retval0 = (columnX + 11).evaluate(object: testObj) + XCTAssertEqual(21, retval0.result) + XCTAssertFalse(retval0.unknown) + let retval1 = (columnY - 11).evaluate(object: testObj) + XCTAssertEqual(1.0 - 11, retval1.result) + XCTAssertFalse(retval1.unknown) + let retval2 = (columnX - 4 == 6).evaluate(object: testObj) + XCTAssertTrue(retval2.result) + XCTAssertFalse(retval2.unknown) + let retval3 = (columnX % 7).evaluate(object: testObj) + XCTAssertEqual(3, retval3.result) + XCTAssertFalse(retval3.unknown) + } + + func testNull() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX10AsNull) + let columnY = FieldExpr(name: "y", primaryKey: false, hasIndex: false, tableReader: testObjYTable, objectReader: testObjY) + let testObj = TestObj() + testObj.x = 10 + testObj.y = 1.0 + let retval0 = (columnX == 10).evaluate(object: testObj) + XCTAssertTrue(retval0.unknown) + let retval1 = ((columnX == 10) && (columnY > 9.0)).evaluate(object: testObj) + XCTAssertFalse(retval1.result) + XCTAssertFalse(retval1.unknown) + let retval2 = ((columnX == 10) && (columnY > 0.0)).evaluate(object: testObj) + XCTAssertTrue(retval2.unknown) + let retval3 = ((columnX == 10) || (columnY > 9.0)).evaluate(object: testObj) + XCTAssertTrue(retval3.unknown) + let retval4 = ((columnX == 10) || (columnY > 0.0)).evaluate(object: testObj) + XCTAssertTrue(retval4.result) + XCTAssertFalse(retval4.unknown) + let retval5 = (columnX != 10).evaluate(object: testObj) + XCTAssertTrue(retval5.unknown) + let retval6 = (!(columnX != 10)).evaluate(object: testObj) + XCTAssertTrue(retval6.unknown) + let retval7 = ((columnX == 10) || (columnY > 9.0)).isNull().evaluate(object: testObj) + XCTAssertTrue(retval7.result) + XCTAssertFalse(retval7.unknown) + let retval8 = ((columnX == 10) || (columnY > 0.0)).isNotNull().evaluate(object: testObj) + XCTAssertTrue(retval8.result) + XCTAssertFalse(retval8.unknown) + let retval9 = columnX.in([10]).evaluate(object: testObj) + XCTAssertTrue(retval9.unknown) + let retval10 = columnX.notIn([10]).evaluate(object: testObj) + XCTAssertTrue(retval10.unknown) + } + + func testInSet() { + let columnX = FieldExpr(name: "x", primaryKey: false, hasIndex: false, tableReader: testObjXTable, objectReader: testObjX) + let testObj = TestObj() + testObj.x = 10 + let retval0 = columnX.in([10, 11, 12]).evaluate(object: testObj) + XCTAssertTrue(retval0.result) + XCTAssertFalse(retval0.unknown) + let retval1 = columnX.in([11, 12]).evaluate(object: testObj) + XCTAssertFalse(retval1.result) + XCTAssertFalse(retval1.unknown) + let retval2 = columnX.notIn([11, 12]).evaluate(object: testObj) + XCTAssertTrue(retval2.result) + XCTAssertFalse(retval2.unknown) + let retval3 = columnX.notIn([10, 11, 12]).evaluate(object: testObj) + XCTAssertFalse(retval3.result) + XCTAssertFalse(retval3.unknown) + } + +} diff --git a/src/tests/Monster.swift b/src/tests/Monster.swift new file mode 100644 index 000000000..4b72e0999 --- /dev/null +++ b/src/tests/Monster.swift @@ -0,0 +1,99 @@ +import Dflat +import FlatBuffers + +public enum MyGame { +public enum Sample { + +public enum Color: Int8 { + case red = 0 + case green = 1 + case blue = 2 +} + +public enum Equipment { + case weapon(_: Weapon) +} + +public struct Vec3 { + var x: Float + var y: Float + var z: Float + public init(_ vec3: FlatBuffers_Generated.MyGame.Sample.Vec3) { + self.x = vec3.x + self.y = vec3.y + self.z = vec3.z + } +} + +public final class Monster: DflatAtom { + let pos: Vec3? + let mana: Int16 + let hp: Int16 + let name: String? + let inventory: [UInt8] + let color: Color + let weapons: [Weapon] + let equipped: Equipment? + let path: [Vec3] + public init(pos: Vec3?, name: String?, inventory: [UInt8], weapons: [Weapon], equipped: Equipment?, path: [Vec3], mana: Int16 = 150, hp: Int16 = 100, color: Color = .blue) { + self.pos = pos + self.mana = mana + self.hp = hp + self.name = name + self.inventory = inventory + self.color = color + self.weapons = weapons + self.equipped = equipped + self.path = path + super.init() + } + + public init(_ monster: FlatBuffers_Generated.MyGame.Sample.Monster) { + self.pos = monster.pos.map { Vec3($0) } + self.mana = monster.mana + self.hp = monster.hp + self.name = monster.name + self.inventory = monster.inventory + var weapons = [Weapon]() + for i: Int32 in 0.. (result: Int32, unknown: Bool) { + return (0, true) +} + +func testObjX(_ object: DflatAtom) -> (result: Int32, unknown: Bool) { + let object: TestObj = object as! TestObj + return (object.x, false) +} + +func testObjX10AsNull(_ object: DflatAtom) -> (result: Int32, unknown: Bool) { + let object: TestObj = object as! TestObj + if object.x == 10 { + return (object.x, true) + } + return (object.x, false) +} + +func testObjYTable(_ table: FlatBufferObject) -> (result: Float, unknown: Bool) { + return (0, true) +} + +func testObjY(_ object: DflatAtom) -> (result: Float, unknown: Bool) { + let object: TestObj = object as! TestObj + return (object.y, false) +} + +class DflatTests: XCTestCase { + + func testDflat() { + let filePath = NSTemporaryDirectory().appending("\(UUID().uuidString).db") + let dflat = SQLiteDflat(filePath: filePath, fileProtectionLevel: .noProtection) + let columnY = FieldExpr(name: "y", primaryKey: true, hasIndex: false, tableReader: testObjYTable, objectReader: testObjY) + let _ = dflat.fetchFor(ofType: TestObj.self).where(columnY > 1.5) + dflat.performChanges([TestObj.self], changesHandler: { (txContext) in + }) + } + +} diff --git a/src/tests/monster_generated.swift b/src/tests/monster_generated.swift new file mode 100644 index 000000000..c6afc687a --- /dev/null +++ b/src/tests/monster_generated.swift @@ -0,0 +1,182 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +import FlatBuffers + +public enum FlatBuffers_Generated { +public enum MyGame { +public enum Sample { + +public enum Color: Int8, Enum { + public typealias T = Int8 + public static var byteSize: Int { return MemoryLayout.size } + public var value: Int8 { return self.rawValue } + case red = 0 + case green = 1 + case blue = 2 + + + public static var max: Color { return .blue } + public static var min: Color { return .red } +} + +public enum Equipment: UInt8, Enum { + public typealias T = UInt8 + public static var byteSize: Int { return MemoryLayout.size } + public var value: UInt8 { return self.rawValue } + case none_ = 0 + case weapon = 1 + + + public static var max: Equipment { return .weapon } + public static var min: Equipment { return .none_ } +} + +public struct Vec3: Readable { + + static func validateVersion() { FlatBuffersVersion_1_12_0() } + public var __buffer: ByteBuffer! { return _accessor.bb } + private var _accessor: Struct + + public static var size = 12 + public static var alignment = 4 + public init(_ bb: ByteBuffer, o: Int32) { _accessor = Struct(bb: bb, position: o) } + + public var x: Float32 { return _accessor.readBuffer(of: Float32.self, at: 0) } + public var y: Float32 { return _accessor.readBuffer(of: Float32.self, at: 4) } + public var z: Float32 { return _accessor.readBuffer(of: Float32.self, at: 8) } +} + +public static func createVec3(x: Float32 = 0.0, y: Float32 = 0.0, z: Float32 = 0.0) -> UnsafeMutableRawPointer { + let memory = UnsafeMutableRawPointer.allocate(byteCount: Vec3.size, alignment: Vec3.alignment) + memory.initializeMemory(as: UInt8.self, repeating: 0, count: Vec3.size) + memory.storeBytes(of: x, toByteOffset: 0, as: Float32.self) + memory.storeBytes(of: y, toByteOffset: 4, as: Float32.self) + memory.storeBytes(of: z, toByteOffset: 8, as: Float32.self) + return memory +} + +public struct Monster: FlatBufferObject { + + static func validateVersion() { FlatBuffersVersion_1_12_0() } + public var __buffer: ByteBuffer! { return _accessor.bb } + private var _accessor: Table + + public static func getRootAsMonster(bb: ByteBuffer) -> Monster { return Monster(Table(bb: bb, position: Int32(bb.read(def: UOffset.self, position: bb.reader)) + Int32(bb.reader))) } + + private init(_ t: Table) { _accessor = t } + public init(_ bb: ByteBuffer, o: Int32) { _accessor = Table(bb: bb, position: o) } + + enum VTOFFSET: VOffset { + case pos = 4 + case mana = 6 + case hp = 8 + case name = 10 + case inventory = 14 + case color = 16 + case weapons = 18 + case equippedType = 20 + case equipped = 22 + case path = 24 + var v: Int32 { Int32(self.rawValue) } + var p: VOffset { self.rawValue } + } + + public var pos: MyGame.Sample.Vec3? { let o = _accessor.offset(VTOFFSET.pos.v); return o == 0 ? nil : MyGame.Sample.Vec3(_accessor.bb, o: o + _accessor.postion) } + public var mana: Int16 { let o = _accessor.offset(VTOFFSET.mana.v); return o == 0 ? 150 : _accessor.readBuffer(of: Int16.self, at: o) } + public var hp: Int16 { let o = _accessor.offset(VTOFFSET.hp.v); return o == 0 ? 100 : _accessor.readBuffer(of: Int16.self, at: o) } + public var name: String? { let o = _accessor.offset(VTOFFSET.name.v); return o == 0 ? nil : _accessor.string(at: o) } + public var nameSegmentArray: [UInt8]? { return _accessor.getVector(at: VTOFFSET.name.v) } + public var inventoryCount: Int32 { let o = _accessor.offset(VTOFFSET.inventory.v); return o == 0 ? 0 : _accessor.vector(count: o) } + public func inventory(at index: Int32) -> UInt8 { let o = _accessor.offset(VTOFFSET.inventory.v); return o == 0 ? 0 : _accessor.directRead(of: UInt8.self, offset: _accessor.vector(at: o) + index * 1) } + public var inventory: [UInt8] { return _accessor.getVector(at: VTOFFSET.inventory.v) ?? [] } + public var color: MyGame.Sample.Color { let o = _accessor.offset(VTOFFSET.color.v); return o == 0 ? .blue : MyGame.Sample.Color(rawValue: _accessor.readBuffer(of: Int8.self, at: o)) ?? .blue } + public var weaponsCount: Int32 { let o = _accessor.offset(VTOFFSET.weapons.v); return o == 0 ? 0 : _accessor.vector(count: o) } + public func weapons(at index: Int32) -> MyGame.Sample.Weapon? { let o = _accessor.offset(VTOFFSET.weapons.v); return o == 0 ? nil : MyGame.Sample.Weapon(_accessor.bb, o: _accessor.indirect(_accessor.vector(at: o) + index * 4)) } + public var equippedType: MyGame.Sample.Equipment { let o = _accessor.offset(VTOFFSET.equippedType.v); return o == 0 ? .none_ : MyGame.Sample.Equipment(rawValue: _accessor.readBuffer(of: UInt8.self, at: o)) ?? .none_ } + public func equipped(type: T.Type) -> T? { let o = _accessor.offset(VTOFFSET.equipped.v); return o == 0 ? nil : _accessor.union(o) } + public var pathCount: Int32 { let o = _accessor.offset(VTOFFSET.path.v); return o == 0 ? 0 : _accessor.vector(count: o) } + public func path(at index: Int32) -> MyGame.Sample.Vec3? { let o = _accessor.offset(VTOFFSET.path.v); return o == 0 ? nil : MyGame.Sample.Vec3(_accessor.bb, o: _accessor.vector(at: o) + index * 12) } + public static func startMonster(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 11) } + public static func add(pos: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(structOffset: VTOFFSET.pos.p) } + public static func add(mana: Int16, _ fbb: inout FlatBufferBuilder) { fbb.add(element: mana, def: 150, at: VTOFFSET.mana.p) } + public static func add(hp: Int16, _ fbb: inout FlatBufferBuilder) { fbb.add(element: hp, def: 100, at: VTOFFSET.hp.p) } + public static func add(name: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: name, at: VTOFFSET.name.p) } + public static func addVectorOf(inventory: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: inventory, at: VTOFFSET.inventory.p) } + public static func add(color: MyGame.Sample.Color, _ fbb: inout FlatBufferBuilder) { fbb.add(element: color.rawValue, def: 2, at: VTOFFSET.color.p) } + public static func addVectorOf(weapons: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: weapons, at: VTOFFSET.weapons.p) } + public static func add(equippedType: MyGame.Sample.Equipment, _ fbb: inout FlatBufferBuilder) { fbb.add(element: equippedType.rawValue, def: 0, at: VTOFFSET.equippedType.p) } + public static func add(equipped: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: equipped, at: VTOFFSET.equipped.p) } + public static func addVectorOf(path: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: path, at: VTOFFSET.path.p) } + public static func endMonster(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end } + public static func createMonster(_ fbb: inout FlatBufferBuilder, + offsetOfPos pos: Offset = Offset(), + mana: Int16 = 150, + hp: Int16 = 100, + offsetOfName name: Offset = Offset(), + vectorOfInventory inventory: Offset = Offset(), + color: MyGame.Sample.Color = .blue, + vectorOfWeapons weapons: Offset = Offset(), + equippedType: MyGame.Sample.Equipment = .none_, + offsetOfEquipped equipped: Offset = Offset(), + vectorOfPath path: Offset = Offset()) -> Offset { + let __start = Monster.startMonster(&fbb) + Monster.add(pos: pos, &fbb) + Monster.add(mana: mana, &fbb) + Monster.add(hp: hp, &fbb) + Monster.add(name: name, &fbb) + Monster.addVectorOf(inventory: inventory, &fbb) + Monster.add(color: color, &fbb) + Monster.addVectorOf(weapons: weapons, &fbb) + Monster.add(equippedType: equippedType, &fbb) + Monster.add(equipped: equipped, &fbb) + Monster.addVectorOf(path: path, &fbb) + return Monster.endMonster(&fbb, start: __start) + } +} + +public struct Weapon: FlatBufferObject { + + static func validateVersion() { FlatBuffersVersion_1_12_0() } + public var __buffer: ByteBuffer! { return _accessor.bb } + private var _accessor: Table + + public static func getRootAsWeapon(bb: ByteBuffer) -> Weapon { return Weapon(Table(bb: bb, position: Int32(bb.read(def: UOffset.self, position: bb.reader)) + Int32(bb.reader))) } + + private init(_ t: Table) { _accessor = t } + public init(_ bb: ByteBuffer, o: Int32) { _accessor = Table(bb: bb, position: o) } + + enum VTOFFSET: VOffset { + case name = 4 + case damage = 6 + var v: Int32 { Int32(self.rawValue) } + var p: VOffset { self.rawValue } + } + + public var name: String? { let o = _accessor.offset(VTOFFSET.name.v); return o == 0 ? nil : _accessor.string(at: o) } + public var nameSegmentArray: [UInt8]? { return _accessor.getVector(at: VTOFFSET.name.v) } + public var damage: Int16 { let o = _accessor.offset(VTOFFSET.damage.v); return o == 0 ? 0 : _accessor.readBuffer(of: Int16.self, at: o) } + public static func startWeapon(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 2) } + public static func add(name: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: name, at: VTOFFSET.name.p) } + public static func add(damage: Int16, _ fbb: inout FlatBufferBuilder) { fbb.add(element: damage, def: 0, at: VTOFFSET.damage.p) } + public static func endWeapon(_ fbb: inout FlatBufferBuilder, start: UOffset) -> Offset { let end = Offset(offset: fbb.endTable(at: start)); return end } + public static func createWeapon(_ fbb: inout FlatBufferBuilder, + offsetOfName name: Offset = Offset(), + damage: Int16 = 0) -> Offset { + let __start = Weapon.startWeapon(&fbb) + Weapon.add(name: name, &fbb) + Weapon.add(damage: damage, &fbb) + return Weapon.endWeapon(&fbb, start: __start) + } +} + +} + +// MARK: - Sample + +} + +// MARK: - MyGame + +} + +// MARK: - FlatBuffers_Generated