File: LMDB%2BEnvironment.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (155 lines) | stat: -rw-r--r-- 7,133 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See https://swift.org/LICENSE.txt for license information
 See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation
import CLMDB

extension LMDB {
    
    /**
     An `Environment` is the base of LMDB.
     It's opened using a file path which can contain 0 or more databases.
     
     - Note: An environment is necessary to open a database.
     */
    class Environment {
        
        /// The set of flags used by the environment.
        /// - Note: [Original documentation](http://www.lmdb.tech/doc/group__mdb__env.html).
        public struct Flags: OptionSet {
            public let rawValue: Int32
            public init(rawValue: Int32) { self.rawValue = rawValue }
            
            public static let fixedMap = Flags(rawValue: MDB_FIXEDMAP)
            public static let noSubDir = Flags(rawValue: MDB_NOSUBDIR)
            public static let noSync = Flags(rawValue: MDB_NOSYNC)
            public static let readOnly = Flags(rawValue: MDB_RDONLY)
            public static let noMetaSync = Flags(rawValue: MDB_NOMETASYNC)
            public static let writeMap = Flags(rawValue: MDB_WRITEMAP)
            public static let mapAsync = Flags(rawValue: MDB_MAPASYNC)
            public static let noTLS = Flags(rawValue: MDB_NOTLS)
            public static let noLock = Flags(rawValue: MDB_NOLOCK)
            public static let noReadahead = Flags(rawValue: MDB_NORDAHEAD)
            public static let noMemoryInit = Flags(rawValue: MDB_NOMEMINIT)
        }
        
        /// Provides access to database handle when in opened state only.
        public private(set) var opaquePointer: OpaquePointer? = nil
        
        /// Provides the information is the Environment is in read-only mode.
        public private(set) var readOnly: Bool = false
        
        /**
         Initialize an environment for a given path using the given flags.
         
         - Parameters:
            - path: The path to open the environment. Note: the path must exist on the disk.
            - maxDBs: The maximum number of databases that can be opened in the environment. Default: infinite.
            - maxReaders: The maximum number of readers to use. Default: 126
            - mapSize: The size of the map on disk in bytes. Default: 10 MB.
            - fileMode: The `LMDB.ModeType` to use when opening a file for the database. Default: 744.
         
         - Throws: An error if the environment can't be initialized correctly.
         */
        public init(path: String, flags: Flags = [], maxDBs: UInt32 = LMDB.defaultMaxDBs, maxReaders: UInt32 = LMDB.defaultMaxReaders, mapSize: size_t = LMDB.defaultMapSize, fileMode: LMDB.ModeType = LMDB.defaultFileMode) throws {

            let result = mdb_env_create(&opaquePointer)
            guard result == 0 else {
                throw Error(errorCode: result)
            }
            
            // Mark the environment as read-only if the flag is set.
            readOnly = flags.contains(.readOnly)
            try configureEnvironment(opaquePointer: opaquePointer, maxDBs: maxDBs, maxReaders: maxReaders, mapSize: mapSize, fileMode: fileMode)
            
            var status = mdb_env_open(opaquePointer, path, UInt32(flags.rawValue), fileMode)
            
            // In case the filesystem is ready-only or the user has not permission to write, just open the environment as read-only and return it.
            if Error(errorCode: status) == Error.readOnlyFileSystem || (Error(errorCode: status) == Error.accessError && !flags.contains(.readOnly)) {
                close() // We need to release the previously failed pointer before performing the fallback process.
                
                guard mdb_env_create(&opaquePointer) == 0 else {
                    throw Error(errorCode: result)
                }
                
                try configureEnvironment(opaquePointer: opaquePointer, maxDBs: maxDBs, maxReaders: maxReaders, mapSize: mapSize, fileMode: fileMode)
                
                status = mdb_env_open(opaquePointer, path, UInt32(flags.union([.readOnly, .noLock]).rawValue), fileMode)
                readOnly = true
            }
            
            guard status == 0 else {
                close()
                throw Error(errorCode: status)
            }
        }
        
        private func configureEnvironment(opaquePointer: OpaquePointer? = nil, maxDBs: UInt32 = LMDB.defaultMaxDBs, maxReaders: UInt32 = LMDB.defaultMaxReaders, mapSize: size_t = LMDB.defaultMapSize, fileMode: LMDB.ModeType = LMDB.defaultFileMode) throws {
            
            if maxDBs != LMDB.defaultMaxDBs {
                let returnCode = mdb_env_set_maxdbs(opaquePointer, MDB_dbi(maxDBs))
                guard returnCode == 0 else {
                    throw Error(errorCode: returnCode)
                }
            }
            
            if maxReaders != LMDB.defaultMaxReaders {
                let returnCode = mdb_env_set_maxreaders(opaquePointer, maxReaders)
                guard returnCode == 0 else {
                    throw Error(errorCode: returnCode)
                }
            }
            
            if mapSize != LMDB.defaultMapSize {
                let returnCode = mdb_env_set_mapsize(opaquePointer, mapSize)
                guard returnCode == 0 else {
                    throw Error(errorCode: returnCode)
                }
            }
            
        }
        
        deinit {
            close()
        }
        
        // MARK: State Management
        
        /// Close the environment and release the memory map.
        public func close() {
            if opaquePointer != nil {
                mdb_env_close(opaquePointer)
                opaquePointer = nil
            }
        }
        
        
        /// Opens a database in the environment.
        /// - Parameters:
        ///   - name: The name of the database. `nil` implies the usage of the anonymous database.
        ///   - flags: The flags to be used to open the database.
        /// - Throws: An error if operation fails. See `LMDB.Error` for reference.
        /// - Returns: An instance of `Database`.
        public func openDatabase(named name: String? = nil, flags: Database.Flags = [.create]) throws -> Database {
            return try Database(environment: self, name: name, flags: flags)
        }
        
        
        /// Creates a transaction for this Environment.
        /// - Parameters:
        ///   - parent: The parent transaction to use. Default: `nil`.
        ///   - readOnly: Defines if a transaction is read-only. Default `false`.
        /// - Returns: Returns a `Transaction` instance for the current Environment.
        public func transaction(parent: Transaction? = nil, readOnly: Bool = false) -> Transaction {
            return Transaction(environment: self, parent: parent, readOnly: readOnly)
        }

    }
}