no message

This commit is contained in:
Peter
2015-09-22 15:24:13 +03:00
parent 341e9a6609
commit ab426e64c2
44 changed files with 18441 additions and 1779 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "submodules/SQLite.swift"]
path = submodules/SQLite.swift
url = git@github.com:peter-iakovlev/SQLite.swift.git
[submodule "submodules/lmdb"]
path = submodules/lmdb
url = https://github.com/LMDB/lmdb.git

View File

@@ -8,9 +8,16 @@
/* Begin PBXBuildFile section */
D003E4E61B38DBDB00C22CBC /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003E4E51B38DBDB00C22CBC /* MessageView.swift */; };
D00E0FB31B84CEDA002E4EB5 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D00E0FB21B84CEDA002E4EB5 /* Display.framework */; };
D00E0FB81B85D192002E4EB5 /* lmdb.h in Headers */ = {isa = PBXBuildFile; fileRef = D00E0FB41B85D192002E4EB5 /* lmdb.h */; };
D00E0FB91B85D192002E4EB5 /* mdb.c in Sources */ = {isa = PBXBuildFile; fileRef = D00E0FB51B85D192002E4EB5 /* mdb.c */; };
D00E0FBA1B85D192002E4EB5 /* midl.c in Sources */ = {isa = PBXBuildFile; fileRef = D00E0FB61B85D192002E4EB5 /* midl.c */; };
D00E0FBB1B85D192002E4EB5 /* midl.h in Headers */ = {isa = PBXBuildFile; fileRef = D00E0FB71B85D192002E4EB5 /* midl.h */; };
D00E0FBE1B85D1B5002E4EB5 /* LmdbValueBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00E0FBD1B85D1B5002E4EB5 /* LmdbValueBox.swift */; };
D044E15E1B2ACB9C001EE087 /* CodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044E15D1B2ACB9C001EE087 /* CodingTests.swift */; };
D044E1631B2AD677001EE087 /* MurMurHash32.m in Sources */ = {isa = PBXBuildFile; fileRef = D044E1621B2AD677001EE087 /* MurMurHash32.m */; };
D044E1641B2AD718001EE087 /* MurMurHash32.h in Headers */ = {isa = PBXBuildFile; fileRef = D044E1611B2AD667001EE087 /* MurMurHash32.h */; settings = {ATTRIBUTES = (Public, ); }; };
D055BD331B7D3D2D00F06C0A /* MediaBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D055BD321B7D3D2D00F06C0A /* MediaBox.swift */; };
D07516441B2D9CEF00AE42E0 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = D07516401B2D9CEF00AE42E0 /* sqlite3.c */; };
D07516451B2D9CEF00AE42E0 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516411B2D9CEF00AE42E0 /* sqlite3.h */; };
D07516461B2D9CEF00AE42E0 /* sqlite3ext.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516421B2D9CEF00AE42E0 /* sqlite3ext.h */; };
@@ -22,13 +29,17 @@
D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */; };
D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */; };
D07516791B2EC90400AE42E0 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */; };
D0977F9C1B822DB4009994B2 /* ValueBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9B1B822DB4009994B2 /* ValueBox.swift */; };
D0977F9E1B8234DF009994B2 /* ValueBoxKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */; };
D0977FA01B8244D7009994B2 /* SqliteValueBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */; };
D0977FA21B82930C009994B2 /* PostboxCodingUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977FA11B82930C009994B2 /* PostboxCodingUtils.swift */; };
D0977FA41B829F87009994B2 /* PostboxTables.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977FA31B829F87009994B2 /* PostboxTables.swift */; };
D0B76BE71B66639F0095CF45 /* DeferredString.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B76BE61B66639F0095CF45 /* DeferredString.swift */; };
D0C07F6A1B67DB4800966E43 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */; };
D0D224F21B4D6ABD0085E26D /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D224ED1B4D6ABD0085E26D /* Functions.swift */; };
D0D225261B4D84930085E26D /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D225251B4D84930085E26D /* PeerView.swift */; };
D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E3A74F1B28A7E300A402D9 /* Postbox.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0E3A7561B28A7E300A402D9 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E3A74A1B28A7E300A402D9 /* Postbox.framework */; };
D0E3A75D1B28A7E300A402D9 /* PostboxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A75C1B28A7E300A402D9 /* PostboxTests.swift */; };
D0E3A7821B28ADD000A402D9 /* Postbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7811B28ADD000A402D9 /* Postbox.swift */; };
D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7831B28AE0900A402D9 /* Peer.swift */; };
D0E3A7881B28AE9C00A402D9 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7871B28AE9C00A402D9 /* Coding.swift */; };
@@ -48,9 +59,16 @@
/* Begin PBXFileReference section */
D003E4E51B38DBDB00C22CBC /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
D00E0FB21B84CEDA002E4EB5 /* Display.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Display.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/Display.framework"; sourceTree = "<group>"; };
D00E0FB41B85D192002E4EB5 /* lmdb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lmdb.h; path = submodules/lmdb/libraries/liblmdb/lmdb.h; sourceTree = SOURCE_ROOT; };
D00E0FB51B85D192002E4EB5 /* mdb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mdb.c; path = submodules/lmdb/libraries/liblmdb/mdb.c; sourceTree = SOURCE_ROOT; };
D00E0FB61B85D192002E4EB5 /* midl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = midl.c; path = submodules/lmdb/libraries/liblmdb/midl.c; sourceTree = SOURCE_ROOT; };
D00E0FB71B85D192002E4EB5 /* midl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = midl.h; path = submodules/lmdb/libraries/liblmdb/midl.h; sourceTree = SOURCE_ROOT; };
D00E0FBD1B85D1B5002E4EB5 /* LmdbValueBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LmdbValueBox.swift; sourceTree = "<group>"; };
D044E15D1B2ACB9C001EE087 /* CodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodingTests.swift; sourceTree = "<group>"; };
D044E1611B2AD667001EE087 /* MurMurHash32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MurMurHash32.h; sourceTree = "<group>"; };
D044E1621B2AD677001EE087 /* MurMurHash32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MurMurHash32.m; sourceTree = "<group>"; };
D055BD321B7D3D2D00F06C0A /* MediaBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaBox.swift; sourceTree = "<group>"; };
D07516401B2D9CEF00AE42E0 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sqlite3.c; sourceTree = "<group>"; };
D07516411B2D9CEF00AE42E0 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3.h; sourceTree = "<group>"; };
D07516421B2D9CEF00AE42E0 /* sqlite3ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3ext.h; sourceTree = "<group>"; };
@@ -63,6 +81,11 @@
D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = "<group>"; };
D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = "<group>"; };
D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = "<group>"; };
D0977F9B1B822DB4009994B2 /* ValueBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueBox.swift; sourceTree = "<group>"; };
D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueBoxKey.swift; sourceTree = "<group>"; };
D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteValueBox.swift; sourceTree = "<group>"; };
D0977FA11B82930C009994B2 /* PostboxCodingUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxCodingUtils.swift; sourceTree = "<group>"; };
D0977FA31B829F87009994B2 /* PostboxTables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxTables.swift; sourceTree = "<group>"; };
D0B76BE61B66639F0095CF45 /* DeferredString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeferredString.swift; sourceTree = "<group>"; };
D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = "<group>"; };
D0D224ED1B4D6ABD0085E26D /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Functions.swift; path = submodules/sqlite.swift/SQLite/Functions.swift; sourceTree = SOURCE_ROOT; };
@@ -72,7 +95,6 @@
D0E3A74F1B28A7E300A402D9 /* Postbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Postbox.h; sourceTree = "<group>"; };
D0E3A7551B28A7E300A402D9 /* PostboxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PostboxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D0E3A75B1B28A7E300A402D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D0E3A75C1B28A7E300A402D9 /* PostboxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxTests.swift; sourceTree = "<group>"; };
D0E3A7811B28ADD000A402D9 /* Postbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Postbox.swift; sourceTree = "<group>"; };
D0E3A7831B28AE0900A402D9 /* Peer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peer.swift; sourceTree = "<group>"; };
D0E3A7871B28AE9C00A402D9 /* Coding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = "<group>"; };
@@ -85,6 +107,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D00E0FB31B84CEDA002E4EB5 /* Display.framework in Frameworks */,
D0C07F6A1B67DB4800966E43 /* SwiftSignalKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -100,9 +123,21 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D00E0FBC1B85D194002E4EB5 /* lmdb */ = {
isa = PBXGroup;
children = (
D00E0FB41B85D192002E4EB5 /* lmdb.h */,
D00E0FB51B85D192002E4EB5 /* mdb.c */,
D00E0FB61B85D192002E4EB5 /* midl.c */,
D00E0FB71B85D192002E4EB5 /* midl.h */,
);
name = lmdb;
sourceTree = "<group>";
};
D07515FC1B2C44A200AE42E0 /* thirdparty */ = {
isa = PBXGroup;
children = (
D00E0FBC1B85D194002E4EB5 /* lmdb */,
D07516731B2EC8C700AE42E0 /* sqlite.swift */,
);
name = thirdparty;
@@ -144,6 +179,7 @@
D0E3A7401B28A7E300A402D9 = {
isa = PBXGroup;
children = (
D00E0FB21B84CEDA002E4EB5 /* Display.framework */,
D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */,
D07516491B2D9E2500AE42E0 /* Postbox.xcconfig */,
D0E3A74C1B28A7E300A402D9 /* Postbox */,
@@ -174,7 +210,14 @@
D0E3A7A11B28B7DC00A402D9 /* Media.swift */,
D003E4E51B38DBDB00C22CBC /* MessageView.swift */,
D0D225251B4D84930085E26D /* PeerView.swift */,
D0977FA11B82930C009994B2 /* PostboxCodingUtils.swift */,
D0E3A7811B28ADD000A402D9 /* Postbox.swift */,
D0977FA31B829F87009994B2 /* PostboxTables.swift */,
D055BD321B7D3D2D00F06C0A /* MediaBox.swift */,
D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */,
D0977F9B1B822DB4009994B2 /* ValueBox.swift */,
D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */,
D00E0FBD1B85D1B5002E4EB5 /* LmdbValueBox.swift */,
D0E3A74D1B28A7E300A402D9 /* Supporting Files */,
);
path = Postbox;
@@ -194,7 +237,6 @@
D0E3A7591B28A7E300A402D9 /* PostboxTests */ = {
isa = PBXGroup;
children = (
D0E3A75C1B28A7E300A402D9 /* PostboxTests.swift */,
D0E3A75A1B28A7E300A402D9 /* Supporting Files */,
D044E15D1B2ACB9C001EE087 /* CodingTests.swift */,
);
@@ -216,7 +258,9 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D00E0FB81B85D192002E4EB5 /* lmdb.h in Headers */,
D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */,
D00E0FBB1B85D192002E4EB5 /* midl.h in Headers */,
D07516451B2D9CEF00AE42E0 /* sqlite3.h in Headers */,
D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */,
D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */,
@@ -327,16 +371,25 @@
D0E3A7821B28ADD000A402D9 /* Postbox.swift in Sources */,
D0E3A79E1B28B50400A402D9 /* Message.swift in Sources */,
D044E1631B2AD677001EE087 /* MurMurHash32.m in Sources */,
D00E0FBE1B85D1B5002E4EB5 /* LmdbValueBox.swift in Sources */,
D07516721B2EC7FE00AE42E0 /* Value.swift in Sources */,
D0977FA41B829F87009994B2 /* PostboxTables.swift in Sources */,
D0E3A7A21B28B7DC00A402D9 /* Media.swift in Sources */,
D0E3A7881B28AE9C00A402D9 /* Coding.swift in Sources */,
D0977F9E1B8234DF009994B2 /* ValueBoxKey.swift in Sources */,
D00E0FB91B85D192002E4EB5 /* mdb.c in Sources */,
D003E4E61B38DBDB00C22CBC /* MessageView.swift in Sources */,
D075165E1B2EC5B500AE42E0 /* module.modulemap in Sources */,
D07516791B2EC90400AE42E0 /* SQLite-Bridging.m in Sources */,
D0977FA01B8244D7009994B2 /* SqliteValueBox.swift in Sources */,
D0D224F21B4D6ABD0085E26D /* Functions.swift in Sources */,
D00E0FBA1B85D192002E4EB5 /* midl.c in Sources */,
D07516711B2EC7FE00AE42E0 /* Statement.swift in Sources */,
D0D225261B4D84930085E26D /* PeerView.swift in Sources */,
D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */,
D055BD331B7D3D2D00F06C0A /* MediaBox.swift in Sources */,
D0977FA21B82930C009994B2 /* PostboxCodingUtils.swift in Sources */,
D0977F9C1B822DB4009994B2 /* ValueBox.swift in Sources */,
D0B76BE71B66639F0095CF45 /* DeferredString.swift in Sources */,
D075166A1B2EC7FE00AE42E0 /* Database.swift in Sources */,
D07516441B2D9CEF00AE42E0 /* sqlite3.c in Sources */,
@@ -348,7 +401,6 @@
buildActionMask = 2147483647;
files = (
D044E15E1B2ACB9C001EE087 /* CodingTests.swift in Sources */,
D0E3A75D1B28A7E300A402D9 /* PostboxTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D0E3A7541B28A7E300A402D9"
BuildableName = "PostboxTests.xctest"
BlueprintName = "PostboxTests"
ReferencedContainer = "container:Postbox.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -9,6 +9,11 @@
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>PostboxTests.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>12</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>

View File

@@ -5,7 +5,7 @@ public protocol Coding {
func encode(encoder: Encoder)
}
private struct EncodableTypeStore {
private final class EncodableTypeStore {
var dict: [Int32 : Decoder -> Coding] = [:]
func decode(typeHash: Int32, decoder: Decoder) -> Coding? {
@@ -17,7 +17,10 @@ private struct EncodableTypeStore {
}
}
private var typeStore = EncodableTypeStore()
private let _typeStore = EncodableTypeStore()
private let typeStore = { () -> EncodableTypeStore in
return _typeStore
}()
public func declareEncodable(type: Any.Type, f: Decoder -> Coding) {
let string = "\(type)"
@@ -28,22 +31,62 @@ public func declareEncodable(type: Any.Type, f: Decoder -> Coding) {
typeStore.dict[murMurHashString32("\(type)")] = f
}
public final class WriteBuffer {
var memory: UnsafeMutablePointer<Void> = nil
public class MemoryBuffer: Equatable, CustomStringConvertible {
var memory: UnsafeMutablePointer<Void>
var capacity: Int
var offset: Int
var length: Int
var freeWhenDone: Bool
public init(memory: UnsafeMutablePointer<Void>, capacity: Int, length: Int, freeWhenDone: Bool) {
self.memory = memory
self.capacity = capacity
self.length = length
self.freeWhenDone = freeWhenDone
}
public init(data: NSData) {
self.memory = UnsafeMutablePointer(data.bytes)
self.capacity = data.length
self.length = data.length
self.freeWhenDone = false
}
public init() {
self.memory = malloc(32)
self.capacity = 32
self.offset = 0
self.memory = nil
self.capacity = 0
self.length = 0
self.freeWhenDone = false
}
deinit {
free(self.memory)
if self.freeWhenDone {
free(self.memory)
}
}
func makeReadBufferAndReset() -> ReadBuffer {
public var description: String {
let hexString = NSMutableString()
let bytes = UnsafeMutablePointer<UInt8>(self.memory)
for i in 0 ..< self.length {
hexString.appendFormat("%02x", UInt(bytes[i]))
}
return hexString as String
}
}
public func ==(lhs: MemoryBuffer, rhs: MemoryBuffer) -> Bool {
return lhs.length == rhs.length && memcmp(lhs.memory, rhs.memory, lhs.length) == 0
}
public final class WriteBuffer: MemoryBuffer {
var offset = 0
public override init() {
super.init(memory: malloc(32), capacity: 32, length: 0, freeWhenDone: true)
}
public func makeReadBufferAndReset() -> ReadBuffer {
let buffer = ReadBuffer(memory: self.memory, length: self.offset, freeWhenDone: true)
self.memory = malloc(32)
self.capacity = 32
@@ -51,7 +94,11 @@ public final class WriteBuffer {
return buffer
}
func makeData() -> NSData {
public func readBufferNoCopy() -> ReadBuffer {
return ReadBuffer(memory: self.memory, length: self.offset, freeWhenDone: false)
}
public func makeData() -> NSData {
return NSData(bytes: self.memory, length: self.offset)
}
@@ -59,33 +106,30 @@ public final class WriteBuffer {
self.offset = 0
}
func write(data: UnsafePointer<Void>, offset: Int, length: Int) {
public func write(data: UnsafePointer<Void>, offset: Int, length: Int) {
if self.offset + length > self.capacity {
self.capacity = self.offset + length + 256
self.memory = realloc(self.memory, self.capacity)
}
memcpy(self.memory + self.offset, data + offset, length)
self.offset += length
self.length = self.offset
}
}
public final class ReadBuffer {
var memory: UnsafeMutablePointer<Void>
var length: Int
var offset : Int
let freeWhenDone: Bool
public final class ReadBuffer: MemoryBuffer {
var offset = 0
init(memory: UnsafeMutablePointer<Void>, length: Int, freeWhenDone: Bool) {
self.memory = memory
self.length = length
self.offset = 0
self.freeWhenDone = freeWhenDone
public init(memory: UnsafeMutablePointer<Void>, length: Int, freeWhenDone: Bool) {
super.init(memory: memory, capacity: length, length: length, freeWhenDone: freeWhenDone)
}
deinit {
if self.freeWhenDone {
free(self.memory)
}
public init(memoryBufferNoCopy: MemoryBuffer) {
super.init(memory: memoryBufferNoCopy.memory, capacity: memoryBufferNoCopy.length, length: memoryBufferNoCopy.length, freeWhenDone: false)
}
func dataNoCopy() -> NSData {
return NSData(bytesNoCopy: self.memory, length: self.length, freeWhenDone: false)
}
func read(data: UnsafeMutablePointer<Void>, offset: Int, length: Int) {
@@ -118,6 +162,10 @@ public final class Encoder {
public init() {
}
public func memoryBuffer() -> MemoryBuffer {
return self.buffer
}
public func makeReadBufferAndReset() -> ReadBuffer {
return self.buffer.makeReadBufferAndReset()
}
@@ -130,20 +178,13 @@ public final class Encoder {
self.buffer.reset()
}
public func encodeKey(key: UnsafePointer<Int8>) {
var length: Int8 = Int8(strlen(key))
public func encodeKey(key: StaticString) {
var length: Int8 = Int8(key.byteSize)
self.buffer.write(&length, offset: 0, length: 1)
self.buffer.write(UnsafePointer<Void>(key), offset: 0, length: Int(length))
self.buffer.write(key.utf8Start, offset: 0, length: Int(length))
}
public func encodeKey(key: Int8) {
var length: Int8 = 1
self.buffer.write(&length, offset: 0, length: 1)
var keyValue = key
self.buffer.write(&keyValue, offset: 0, length: Int(length))
}
public func encodeInt32(value: Int32, forKey key: UnsafePointer<Int8>) {
public func encodeInt32(value: Int32, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Int32.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -151,15 +192,7 @@ public final class Encoder {
self.buffer.write(&v, offset: 0, length: 4)
}
public func encodeInt32(value: Int32, forKey key: Int8) {
self.encodeKey(key)
var type: Int8 = ValueType.Int32.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var v = value
self.buffer.write(&v, offset: 0, length: 4)
}
public func encodeInt64(value: Int64, forKey key: UnsafePointer<Int8>) {
public func encodeInt64(value: Int64, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Int64.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -167,15 +200,7 @@ public final class Encoder {
self.buffer.write(&v, offset: 0, length: 8)
}
public func encodeInt64(value: Int64, forKey key: Int8) {
self.encodeKey(key)
var type: Int8 = ValueType.Int64.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var v = value
self.buffer.write(&v, offset: 0, length: 8)
}
public func encodeBool(value: Bool, forKey key: UnsafePointer<Int8>) {
public func encodeBool(value: Bool, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Bool.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -183,15 +208,7 @@ public final class Encoder {
self.buffer.write(&v, offset: 0, length: 1)
}
public func encodeBool(value: Bool, forKey key: Int8) {
self.encodeKey(key)
var type: Int8 = ValueType.Bool.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var v: Int8 = value ? 1 : 0
self.buffer.write(&v, offset: 0, length: 1)
}
public func encodeDouble(value: Double, forKey key: UnsafePointer<Int8>) {
public func encodeDouble(value: Double, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Double.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -199,15 +216,7 @@ public final class Encoder {
self.buffer.write(&v, offset: 0, length: 8)
}
public func encodeDouble(value: Double, forKey key: Int8) {
self.encodeKey(key)
var type: Int8 = ValueType.Double.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var v = value
self.buffer.write(&v, offset: 0, length: 8)
}
public func encodeString(value: String, forKey key: UnsafePointer<Int8>) {
public func encodeString(value: String, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.String.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -217,7 +226,7 @@ public final class Encoder {
self.buffer.write(data.bytes, offset: 0, length: Int(length))
}
public func encodeString(value: DeferredString, forKey key: UnsafePointer<Int8>) {
public func encodeString(value: DeferredString, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.String.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -227,21 +236,11 @@ public final class Encoder {
self.buffer.write(data.bytes, offset: 0, length: Int(length))
}
public func encodeString(value: String, forKey key: Int8) {
self.encodeKey(key)
var type: Int8 = ValueType.String.rawValue
self.buffer.write(&type, offset: 0, length: 1)
let data = value.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
var length: Int32 = Int32(data.length)
self.buffer.write(&length, offset: 0, length: 4)
self.buffer.write(data.bytes, offset: 0, length: Int(length))
}
public func encodeRootObject(value: Coding) {
self.encodeObject(value, forKey: "_")
}
public func encodeObject(value: Coding, forKey key: UnsafePointer<Int8>) {
public func encodeObject(value: Coding, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Object.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -258,7 +257,7 @@ public final class Encoder {
self.buffer.write(innerEncoder.buffer.memory, offset: 0, length: Int(length))
}
public func encodeInt32Array(value: [Int32], forKey key: UnsafePointer<Int8>) {
public func encodeInt32Array(value: [Int32], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Int32Array.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -270,7 +269,7 @@ public final class Encoder {
}
}
public func encodeInt64Array(value: [Int64], forKey key: UnsafePointer<Int8>) {
public func encodeInt64Array(value: [Int64], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Int64Array.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -282,7 +281,7 @@ public final class Encoder {
}
}
public func encodeObjectArray<T: Coding>(value: [T], forKey key: UnsafePointer<Int8>) {
public func encodeObjectArray<T: Coding>(value: [T], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.ObjectArray.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -302,7 +301,27 @@ public final class Encoder {
}
}
public func encodeObjectDictionary<K, V: Coding where K: Coding, K: Hashable>(value: [K : V], forKey key: UnsafePointer<Int8>) {
public func encodeObjectArray(value: [Coding], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.ObjectArray.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var length: Int32 = Int32(value.count)
self.buffer.write(&length, offset: 0, length: 4)
let innerEncoder = Encoder()
for object in value {
var typeHash: Int32 = murMurHashString32("\(object.dynamicType)")
self.buffer.write(&typeHash, offset: 0, length: 4)
innerEncoder.reset()
object.encode(innerEncoder)
var length: Int32 = Int32(innerEncoder.buffer.offset)
self.buffer.write(&length, offset: 0, length: 4)
self.buffer.write(innerEncoder.buffer.memory, offset: 0, length: Int(length))
}
}
public func encodeObjectDictionary<K, V: Coding where K: Coding, K: Hashable>(value: [K : V], forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.ObjectDictionary.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -329,7 +348,16 @@ public final class Encoder {
}
}
public func encodeBytes(bytes: WriteBuffer, forKey key: UnsafePointer<Int8>) {
public func encodeBytes(bytes: WriteBuffer, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Bytes.rawValue
self.buffer.write(&type, offset: 0, length: 1)
var bytesLength: Int32 = Int32(bytes.offset)
self.buffer.write(&bytesLength, offset: 0, length: 4)
self.buffer.write(bytes.memory, offset: 0, length: bytes.offset)
}
public func encodeBytes(bytes: ReadBuffer, forKey key: StaticString) {
self.encodeKey(key)
var type: Int8 = ValueType.Bytes.rawValue
self.buffer.write(&type, offset: 0, length: 1)
@@ -342,9 +370,10 @@ public final class Encoder {
}
public final class Decoder {
private let buffer: ReadBuffer
private let buffer: MemoryBuffer
private var offset: Int = 0
public init(buffer: ReadBuffer) {
public init(buffer: MemoryBuffer) {
self.buffer = buffer
}
@@ -407,11 +436,11 @@ public final class Decoder {
}
}
private class func positionOnKey(bytes: UnsafePointer<Int8>, inout offset: Int, maxOffset: Int, length: Int, key: UnsafePointer<Int8>, valueType: ValueType) -> Bool
private class func positionOnKey(bytes: UnsafePointer<Int8>, inout offset: Int, maxOffset: Int, length: Int, key: StaticString, valueType: ValueType) -> Bool
{
let startOffset = offset
let keyLength: Int = Int(strlen(key))
let keyLength: Int = key.byteSize
while (offset < maxOffset)
{
let readKeyLength = bytes[offset]
@@ -421,7 +450,7 @@ public final class Decoder {
let readValueType = bytes[offset]
offset += 1
if readValueType != valueType.rawValue || keyLength != Int(readKeyLength) || memcmp(bytes + (offset - Int(readKeyLength) - 1), key, keyLength) != 0 {
if readValueType != valueType.rawValue || keyLength != Int(readKeyLength) || memcmp(bytes + (offset - Int(readKeyLength) - 1), key.utf8Start, keyLength) != 0 {
skipValue(bytes, offset: &offset, length: length, valueType: ValueType(rawValue: readValueType)!)
} else {
return true
@@ -468,67 +497,78 @@ public final class Decoder {
return false
}
public func decodeInt32ForKey(key: UnsafePointer<Int8>) -> Int32 {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32) {
public func decodeInt32ForKey(key: StaticString) -> Int32 {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32) {
var value: Int32 = 0
memcpy(&value, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&value, self.buffer.memory + self.offset, 4)
self.offset += 4
return value
} else {
return 0
}
}
public func decodeInt32ForKey(key: UnsafePointer<Int8>) -> Int32? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32) {
public func decodeInt32ForKey(key: StaticString) -> Int32? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32) {
var value: Int32 = 0
memcpy(&value, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&value, self.buffer.memory + self.offset, 4)
self.offset += 4
return value
} else {
return nil
}
}
public func decodeInt64ForKey(key: UnsafePointer<Int8>) -> Int64 {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int64) {
public func decodeInt64ForKey(key: StaticString) -> Int64 {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int64) {
var value: Int64 = 0
memcpy(&value, self.buffer.memory + self.buffer.offset, 8)
self.buffer.offset += 8
memcpy(&value, self.buffer.memory + self.offset, 8)
self.offset += 8
return value
} else {
return 0
}
}
public func decodeBoolForKey(key: UnsafePointer<Int8>) -> Bool {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Bool) {
public func decodeInt64ForKey(key: StaticString) -> Int64? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int64) {
var value: Int64 = 0
memcpy(&value, self.buffer.memory + self.offset, 8)
self.offset += 8
return value
} else {
return nil
}
}
public func decodeBoolForKey(key: StaticString) -> Bool {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Bool) {
var value: Int8 = 0
memcpy(&value, self.buffer.memory + self.buffer.offset, 1)
self.buffer.offset += 1
memcpy(&value, self.buffer.memory + self.offset, 1)
self.offset += 1
return value != 0
} else {
return false
}
}
public func decodeDoubleForKey(key: UnsafePointer<Int8>) -> Double {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Double) {
public func decodeDoubleForKey(key: StaticString) -> Double {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Double) {
var value: Double = 0
memcpy(&value, self.buffer.memory + self.buffer.offset, 8)
self.buffer.offset += 8
memcpy(&value, self.buffer.memory + self.offset, 8)
self.offset += 8
return value
} else {
return 0
}
}
public func decodeStringForKey(key: UnsafePointer<Int8>) -> String {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
public func decodeStringForKey(key: StaticString) -> String {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length))
self.buffer.offset += 4 + Int(length)
memcpy(&length, self.buffer.memory + self.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.offset + 4), length: Int(length))
self.offset += 4 + Int(length)
let value = NSString(data: data, encoding: NSUTF8StringEncoding)
return (value as? String) ?? ""
} else {
@@ -536,12 +576,12 @@ public final class Decoder {
}
}
public func decodeStringForKey(key: UnsafePointer<Int8>) -> String? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
public func decodeStringForKey(key: StaticString) -> String? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length))
self.buffer.offset += 4 + Int(length)
memcpy(&length, self.buffer.memory + self.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.offset + 4), length: Int(length))
self.offset += 4 + Int(length)
let value = NSString(data: data, encoding: NSUTF8StringEncoding)
return value as? String
} else {
@@ -549,24 +589,24 @@ public final class Decoder {
}
}
public func decodeStringForKey(key: UnsafePointer<Int8>) -> DeferredString {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
public func decodeStringForKey(key: StaticString) -> DeferredString {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length))
self.buffer.offset += 4 + Int(length)
memcpy(&length, self.buffer.memory + self.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.offset + 4), length: Int(length))
self.offset += 4 + Int(length)
return DeferredStringValue(data)
} else {
return DeferredStringValue("")
}
}
public func decodeStringForKey(key: UnsafePointer<Int8>) -> DeferredString? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
public func decodeStringForKey(key: StaticString) -> DeferredString? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length))
self.buffer.offset += 4 + Int(length)
memcpy(&length, self.buffer.memory + self.offset, 4)
let data = NSData(bytes: self.buffer.memory + (self.offset + 4), length: Int(length))
self.offset += 4 + Int(length)
return DeferredStringValue(data)
} else {
return nil
@@ -577,17 +617,17 @@ public final class Decoder {
return self.decodeObjectForKey("_")
}
public func decodeObjectForKey(key: UnsafePointer<Int8>) -> Coding? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Object) {
public func decodeObjectForKey(key: StaticString) -> Coding? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Object) {
var typeHash: Int32 = 0
memcpy(&typeHash, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&typeHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
memcpy(&length, self.buffer.memory + self.offset, 4)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.buffer.offset + 4), length: Int(length), freeWhenDone: false))
self.buffer.offset += 4 + Int(length)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(length), freeWhenDone: false))
self.offset += 4 + Int(length)
return typeStore.decode(typeHash, decoder: innerDecoder)
} else {
@@ -595,17 +635,17 @@ public final class Decoder {
}
}
public func decodeObjectForKey(key: UnsafePointer<Int8>, decoder: Decoder -> Coding) -> Coding? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Object) {
public func decodeObjectForKey(key: StaticString, decoder: Decoder -> Coding) -> Coding? {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Object) {
var typeHash: Int32 = 0
memcpy(&typeHash, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&typeHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
memcpy(&length, self.buffer.memory + self.offset, 4)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.buffer.offset + 4), length: Int(length), freeWhenDone: false))
self.buffer.offset += 4 + Int(length)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(length), freeWhenDone: false))
self.offset += 4 + Int(length)
return decoder(innerDecoder)
} else {
@@ -613,51 +653,51 @@ public final class Decoder {
}
}
public func decodeInt32ArrayForKey(key: UnsafePointer<Int8>) -> [Int32] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32Array) {
public func decodeInt32ArrayForKey(key: StaticString) -> [Int32] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32Array) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
memcpy(&length, self.buffer.memory + self.offset, 4)
var array: [Int32] = []
array.reserveCapacity(Int(length))
var i: Int32 = 0
while i < length {
var element: Int32 = 0
memcpy(&element, self.buffer.memory + (self.buffer.offset + 4 + 4 * Int(i)), 4)
memcpy(&element, self.buffer.memory + (self.offset + 4 + 4 * Int(i)), 4)
array.append(element)
i++
}
self.buffer.offset += 4 + Int(length) * 4
self.offset += 4 + Int(length) * 4
return array
} else {
return []
}
}
public func decodeInt64ArrayForKey(key: UnsafePointer<Int8>) -> [Int64] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int64Array) {
public func decodeInt64ArrayForKey(key: StaticString) -> [Int64] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int64Array) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
memcpy(&length, self.buffer.memory + self.offset, 4)
var array: [Int64] = []
array.reserveCapacity(Int(length))
var i: Int32 = 0
while i < length {
var element: Int64 = 0
memcpy(&element, self.buffer.memory + (self.buffer.offset + 4 + 8 * Int(i)), 8)
memcpy(&element, self.buffer.memory + (self.offset + 4 + 8 * Int(i)), 8)
array.append(element)
i++
}
self.buffer.offset += 4 + Int(length) * 8
self.offset += 4 + Int(length) * 8
return array
} else {
return []
}
}
public func decodeObjectArrayForKey<T: Coding>(key: UnsafePointer<Int8>) -> [T] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectArray) {
public func decodeObjectArrayForKey<T where T: Coding>(key: StaticString) -> [T] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectArray) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&length, self.buffer.memory + self.offset, 4)
self.offset += 4
var array: [T] = []
array.reserveCapacity(Int(length))
@@ -666,14 +706,14 @@ public final class Decoder {
var i: Int32 = 0
while i < length {
var typeHash: Int32 = 0
memcpy(&typeHash, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&typeHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var objectLength: Int32 = 0
memcpy(&objectLength, self.buffer.memory + self.buffer.offset, 4)
memcpy(&objectLength, self.buffer.memory + self.offset, 4)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.buffer.offset + 4), length: Int(objectLength), freeWhenDone: false))
self.buffer.offset += 4 + Int(objectLength)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(objectLength), freeWhenDone: false))
self.offset += 4 + Int(objectLength)
if !failed {
if let object = typeStore.decode(typeHash, decoder: innerDecoder) as? T {
@@ -696,11 +736,54 @@ public final class Decoder {
}
}
public func decodeObjectDictionaryForKey<K, V: Coding where K: Coding, K: Hashable>(key: UnsafePointer<Int8>) -> [K : V] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectDictionary) {
public func decodeObjectArrayForKey(key: StaticString) -> [Coding] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectArray) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&length, self.buffer.memory + self.offset, 4)
self.offset += 4
var array: [Coding] = []
array.reserveCapacity(Int(length))
var failed = false
var i: Int32 = 0
while i < length {
var typeHash: Int32 = 0
memcpy(&typeHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var objectLength: Int32 = 0
memcpy(&objectLength, self.buffer.memory + self.offset, 4)
let innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(objectLength), freeWhenDone: false))
self.offset += 4 + Int(objectLength)
if !failed {
if let object = typeStore.decode(typeHash, decoder: innerDecoder) {
array.append(object)
} else {
failed = true
}
}
i++
}
if failed {
return []
} else {
return array
}
} else {
return []
}
}
public func decodeObjectDictionaryForKey<K, V: Coding where K: Coding, K: Hashable>(key: StaticString) -> [K : V] {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .ObjectDictionary) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.offset, 4)
self.offset += 4
var dictionary: [K : V] = [:]
@@ -708,26 +791,26 @@ public final class Decoder {
var i: Int32 = 0
while i < length {
var keyHash: Int32 = 0
memcpy(&keyHash, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&keyHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var keyLength: Int32 = 0
memcpy(&keyLength, self.buffer.memory + self.buffer.offset, 4)
memcpy(&keyLength, self.buffer.memory + self.offset, 4)
var innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.buffer.offset + 4), length: Int(keyLength), freeWhenDone: false))
self.buffer.offset += 4 + Int(keyLength)
var innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(keyLength), freeWhenDone: false))
self.offset += 4 + Int(keyLength)
let key = failed ? nil : (typeStore.decode(keyHash, decoder: innerDecoder) as? K)
var valueHash: Int32 = 0
memcpy(&valueHash, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4
memcpy(&valueHash, self.buffer.memory + self.offset, 4)
self.offset += 4
var valueLength: Int32 = 0
memcpy(&valueLength, self.buffer.memory + self.buffer.offset, 4)
memcpy(&valueLength, self.buffer.memory + self.offset, 4)
innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.buffer.offset + 4), length: Int(valueLength), freeWhenDone: false))
self.buffer.offset += 4 + Int(valueLength)
innerDecoder = Decoder(buffer: ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(valueLength), freeWhenDone: false))
self.offset += 4 + Int(valueLength)
let value = failed ? nil : (typeStore.decode(valueHash, decoder: innerDecoder) as? V)
@@ -750,12 +833,12 @@ public final class Decoder {
}
}
public func decodeBytesForKeyNoCopy(key: UnsafePointer<Int8>) -> ReadBuffer! {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Bytes) {
public func decodeBytesForKeyNoCopy(key: StaticString) -> ReadBuffer! {
if Decoder.positionOnKey(UnsafePointer<Int8>(self.buffer.memory), offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Bytes) {
var length: Int32 = 0
memcpy(&length, self.buffer.memory + self.buffer.offset, 4)
self.buffer.offset += 4 + Int(length)
return ReadBuffer(memory: UnsafeMutablePointer<Int8>(self.buffer.memory + (self.buffer.offset - Int(length))), length: Int(length), freeWhenDone: false)
memcpy(&length, self.buffer.memory + self.offset, 4)
self.offset += 4 + Int(length)
return ReadBuffer(memory: UnsafeMutablePointer<Int8>(self.buffer.memory + (self.offset - Int(length))), length: Int(length), freeWhenDone: false)
} else {
return nil
}

395
Postbox/LmdbValueBox.swift Normal file
View File

@@ -0,0 +1,395 @@
import Foundation
import sqlcipher
private struct LmdbTable {
var dbi: MDB_dbi
}
private struct LmdbCursor {
var cursor: COpaquePointer
func seekTo(key: ValueBoxKey, forward: Bool) -> (ValueBoxKey, ReadBuffer)? {
var mdbKey = MDB_val()
var mdbData = MDB_val()
mdbKey.mv_data = key.memory
mdbKey.mv_size = key.length
mdbData.mv_data = nil
mdbData.mv_size = 0
let result = mdb_cursor_get(self.cursor, &mdbKey, &mdbData, forward ? MDB_SET_RANGE : MDB_SET_KEY)
if result == MDB_SUCCESS {
let actualKey = ValueBoxKey(length: mdbKey.mv_size)
memcpy(actualKey.memory, mdbKey.mv_data, mdbKey.mv_size)
let value = malloc(mdbData.mv_size)
memcpy(value, mdbData.mv_data, mdbData.mv_size)
return (actualKey, ReadBuffer(memory: value, length: mdbData.mv_size, freeWhenDone: true))
} else if result == MDB_NOTFOUND {
if !forward {
return self.previous()
} else {
return nil
}
} else {
print("(LmdbValueBox mdb_cursor_get failed with \(result))")
return nil
}
}
func previous() -> (ValueBoxKey, ReadBuffer)? {
var mdbKey = MDB_val()
var mdbData = MDB_val()
mdbKey.mv_data = nil
mdbKey.mv_size = 0
mdbData.mv_data = nil
mdbData.mv_size = 0
let result = mdb_cursor_get(self.cursor, &mdbKey, &mdbData, MDB_PREV)
if result == MDB_SUCCESS {
let actualKey = ValueBoxKey(length: mdbKey.mv_size)
memcpy(actualKey.memory, mdbKey.mv_data, mdbKey.mv_size)
let value = malloc(mdbData.mv_size)
memcpy(value, mdbData.mv_data, mdbData.mv_size)
return (actualKey, ReadBuffer(memory: value, length: mdbData.mv_size, freeWhenDone: true))
} else if result == MDB_NOTFOUND {
return nil
} else {
print("(LmdbValueBox mdb_cursor_get failed with \(result))")
return nil
}
}
func next() -> (ValueBoxKey, ReadBuffer)? {
var mdbKey = MDB_val()
var mdbData = MDB_val()
mdbKey.mv_data = nil
mdbKey.mv_size = 0
mdbData.mv_data = nil
mdbData.mv_size = 0
let result = mdb_cursor_get(self.cursor, &mdbKey, &mdbData, MDB_NEXT)
if result == MDB_SUCCESS {
let actualKey = ValueBoxKey(length: mdbKey.mv_size)
memcpy(actualKey.memory, mdbKey.mv_data, mdbKey.mv_size)
let value = malloc(mdbData.mv_size)
memcpy(value, mdbData.mv_data, mdbData.mv_size)
return (actualKey, ReadBuffer(memory: value, length: mdbData.mv_size, freeWhenDone: true))
} else if result == MDB_NOTFOUND {
return nil
} else {
print("(LmdbValueBox mdb_cursor_get failed with \(result))")
return nil
}
}
}
public final class LmdbValueBox: ValueBox {
private var env: COpaquePointer = nil
private var tables: [Int32 : LmdbTable] = [:]
private var sharedTxn: COpaquePointer = nil
public init?(basePath: String) {
var result = mdb_env_create(&self.env)
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_env_create failed with \(result))")
return nil
}
let path = basePath + "/lmdb"
var createDirectory = false
var isDirectory: ObjCBool = false as ObjCBool
if NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDirectory) {
if !isDirectory {
do {
try NSFileManager.defaultManager().removeItemAtPath(path)
} catch _ { }
createDirectory = true
}
}
else {
createDirectory = true
}
if createDirectory {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(path, withIntermediateDirectories: true, attributes: nil)
} catch _ { }
}
mdb_env_set_mapsize(self.env, 500 * 1024 * 1024);
mdb_env_set_maxdbs(self.env, 64)
path.withCString { string in
result = mdb_env_open(self.env, string, UInt32(MDB_NOSYNC), 0o664)
}
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_env_open failed with \(result))")
return nil
}
var removedReaders: Int32 = 0
result = mdb_reader_check(self.env, &removedReaders)
if removedReaders != 0 {
print("(LmdbValueBox removed \(removedReaders) stale readers)")
}
}
deinit {
mdb_env_close(self.env)
}
private func createTableWithName(name: Int32) -> LmdbTable? {
var dbi = MDB_dbi()
let result = mdb_dbi_open(self.sharedTxn, "\(name)", UInt32(MDB_CREATE), &dbi)
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_dbi_open failed with \(result))")
return nil
}
return LmdbTable(dbi: dbi)
}
public func begin() {
if self.sharedTxn != nil {
print("(LmdbValueBox already in transaction)")
} else {
let result = mdb_txn_begin(self.env, nil, 0, &sharedTxn)
if result != MDB_SUCCESS {
print("(LmdbValueBox txn_begin failed with \(result))")
return
}
}
}
public func commit() {
if self.sharedTxn == nil {
print("(LmdbValueBox already no current transaction)")
} else {
let result = mdb_txn_commit(self.sharedTxn)
self.sharedTxn = nil
if result != MDB_SUCCESS {
print("(LmdbValueBox txn_commit failed with \(result))")
return
}
}
}
public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, @noescape values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int) {
if start == end || limit == 0 {
return
}
var commit = false
if self.sharedTxn == nil {
self.begin()
commit = true
}
var nativeTable: LmdbTable?
if let existingTable = self.tables[table] {
nativeTable = existingTable
} else if let createdTable = self.createTableWithName(table) {
nativeTable = createdTable
self.tables[table] = createdTable
}
if let nativeTable = nativeTable {
var cursorPtr: COpaquePointer = nil
let result = mdb_cursor_open(self.sharedTxn, nativeTable.dbi, &cursorPtr)
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_cursor_open failed with \(result))")
} else {
let cursor = LmdbCursor(cursor: cursorPtr)
if start < end {
var value = cursor.seekTo(start, forward: true)
if value != nil {
if value!.0 == start {
value = cursor.next()
}
}
var count = 0
if value != nil && value!.0 < end {
count++
values(value!.0, value!.1)
}
while value != nil && value!.0 < end && count < limit {
value = cursor.next()
if value != nil && value!.0 < end {
count++
values(value!.0, value!.1)
}
}
} else {
var value = cursor.seekTo(start, forward: false)
if value != nil {
if value!.0 == start {
value = cursor.previous()
}
}
var count = 0
if value != nil && value!.0 > end {
count++
values(value!.0, value!.1)
}
while value != nil && value!.0 > end && count < limit {
value = cursor.previous()
if value != nil && value!.0 > end {
count++
values(value!.0, value!.1)
}
}
}
mdb_cursor_close(cursorPtr)
}
}
if commit {
self.commit()
}
}
public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, keys: ValueBoxKey -> Bool, limit: Int) {
self.range(table, start: start, end: end, values: { key, _ in
return keys(key)
}, limit: limit)
}
public func get(table: Int32, key: ValueBoxKey) -> ReadBuffer? {
var commit = false
if self.sharedTxn == nil {
self.begin()
commit = true
}
var nativeTable: LmdbTable?
if let existingTable = self.tables[table] {
nativeTable = existingTable
} else if let createdTable = self.createTableWithName(table) {
nativeTable = createdTable
self.tables[table] = createdTable
}
var resultValue: ReadBuffer?
if let nativeTable = nativeTable {
var mdbKey = MDB_val()
var mdbData = MDB_val()
mdbKey.mv_data = key.memory
mdbKey.mv_size = key.length
let result = mdb_get(self.sharedTxn, nativeTable.dbi, &mdbKey, &mdbData)
if result == MDB_SUCCESS {
let value = malloc(mdbData.mv_size)
memcpy(value, mdbData.mv_data, mdbData.mv_size)
resultValue = ReadBuffer(memory: value, length: mdbData.mv_size, freeWhenDone: true)
} else {
if result != MDB_NOTFOUND {
print("(LmdbValueBox mdb_get failed with \(result))")
}
}
}
if commit {
self.commit()
}
return resultValue
}
public func exists(table: Int32, key: ValueBoxKey) -> Bool {
return self.get(table, key: key) != nil
}
public func set(table: Int32, key: ValueBoxKey, value: MemoryBuffer) {
var commit = false
if self.sharedTxn == nil {
self.begin()
commit = true
}
var nativeTable: LmdbTable?
if let existingTable = self.tables[table] {
nativeTable = existingTable
} else if let createdTable = self.createTableWithName(table) {
nativeTable = createdTable
self.tables[table] = createdTable
}
if let nativeTable = nativeTable {
var mdbKey = MDB_val()
var mdbData = MDB_val()
mdbKey.mv_data = key.memory
mdbKey.mv_size = key.length
mdbData.mv_data = value.memory
mdbData.mv_size = value.length
let result = mdb_put(self.sharedTxn, nativeTable.dbi, &mdbKey, &mdbData, 0)
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_set failed with \(result))")
}
}
if commit {
self.commit()
}
}
public func remove(table: Int32, key: ValueBoxKey) {
var commit = false
if self.sharedTxn == nil {
self.begin()
commit = true
}
var nativeTable: LmdbTable?
if let existingTable = self.tables[table] {
nativeTable = existingTable
} else if let createdTable = self.createTableWithName(table) {
nativeTable = createdTable
self.tables[table] = createdTable
}
if let nativeTable = nativeTable {
var mdbKey = MDB_val()
mdbKey.mv_data = key.memory
mdbKey.mv_size = key.length
let result = mdb_del(self.sharedTxn, nativeTable.dbi, &mdbKey, nil)
if result != MDB_SUCCESS {
print("(LmdbValueBox mdb_set failed with \(result))")
}
}
if commit {
self.commit()
}
}
public func drop() {
}
}

View File

@@ -9,7 +9,7 @@ public struct MediaId: Hashable, CustomStringConvertible {
public var hashValue: Int {
get {
return Int((self.id & 0xffffffff) ^ ((self.id >> 32) & 0xffffffff))
return Int((self.id & 0x7fffffff) ^ ((self.id >> 32) & 0x7fffffff))
}
}
@@ -67,5 +67,5 @@ public func ==(lhs: MediaId, rhs: MediaId) -> Bool {
}
public protocol Media: Coding {
var id: MediaId { get }
var id: MediaId? { get }
}

145
Postbox/MediaBox.swift Normal file
View File

@@ -0,0 +1,145 @@
import Foundation
import SwiftSignalKit
import Display
public struct MediaKeyStatus {
let size: Int
}
private final class MediaBoxMutexEntry {
var mutex = pthread_mutex_t()
init() {
var attr = pthread_mutexattr_t()
pthread_mutexattr_init(&attr)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)
pthread_mutex_init(&mutex, &attr)
}
deinit {
pthread_mutex_destroy(&mutex)
}
func with(@noescape f: Void -> Void) {
pthread_mutex_lock(&self.mutex)
f()
pthread_mutex_unlock(&self.mutex)
}
}
public final class MediaBox {
let basePath: String
let buffer = WriteBuffer()
private var mutexEntriesLock = OSSpinLock()
private var mutexEntries: [String : MediaBoxMutexEntry] = [:]
lazy var ensureDirectoryCreated: Void = {
try! NSFileManager.defaultManager().createDirectoryAtPath(self.basePath, withIntermediateDirectories: true, attributes: nil)
}()
public init(basePath: String) {
self.basePath = basePath
}
private func keyForId(id: MediaId, key: MemoryBuffer) -> String {
let string = NSMutableString()
string.appendFormat("%d", Int(id.namespace))
string.appendFormat("_%lld", Int64(id.id))
string.appendString("_\(key)")
return string as String
}
private func pathForId(id: MediaId, key: MemoryBuffer) -> String {
return "\(self.basePath)/\(self.keyForId(id, key: key))"
}
private func mutexForKey(key: String) -> MediaBoxMutexEntry {
let entry: MediaBoxMutexEntry
OSSpinLockLock(&mutexEntriesLock)
if let existingEntry = self.mutexEntries[key] {
entry = existingEntry
} else {
entry = MediaBoxMutexEntry()
self.mutexEntries[key] = entry
}
OSSpinLockUnlock(&mutexEntriesLock)
return entry
}
func writeId(id: MediaId, key: ReadBuffer, value: NSData) {
assertNotOnMainThread()
self.mutexForKey(self.keyForId(id, key: key)).with {
let _ = self.ensureDirectoryCreated
value.writeToFile(self.pathForId(id, key: key), atomically: false)
}
}
private func status(id: MediaId, key: ReadBuffer) -> MediaKeyStatus? {
assertNotOnMainThread()
var value = stat()
stat(self.pathForId(id, key: key), &value)
return MediaKeyStatus(size: Int(value.st_size))
}
private func read(id: MediaId, key: ReadBuffer) -> NSData? {
assertNotOnMainThread()
var data: NSData?
do {
data = try NSData(contentsOfFile: self.pathForId(id, key: key), options: NSDataReadingOptions.DataReadingMappedIfSafe)
} catch _ {
}
return data
}
public func data(id: MediaId, key: ReadBuffer, size: Int, fetch: ((NSData, Int) -> Signal<NSData, NoError>)?) -> Signal<NSData, NoError> {
return Signal { subscriber in
var cancelled = false
let disposable = MetaDisposable()
disposable.set(ActionDisposable {
cancelled = true
})
self.mutexForKey(self.keyForId(id, key: key)).with {
if cancelled {
return
}
let status = self.status(id, key: key) ?? MediaKeyStatus(size: 0)
var currentData: NSData?
if status.size > 0 {
if let data = self.read(id, key: key) {
currentData = data
subscriber.putNext(data)
}
}
if status.size < size {
if let fetch = fetch {
disposable.set(fetch(currentData ?? NSData(), status.size).start(next: { [weak self] next in
if let strongSelf = self {
strongSelf.mutexForKey(strongSelf.keyForId(id, key: key)).with {
strongSelf.writeId(id, key: key, value: next)
}
}
subscriber.putNext(next)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
} else {
subscriber.putError(NoError())
}
} else {
subscriber.putCompletion()
}
}
return disposable
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
import Foundation
func peerViewEntryIndexForBuffer(buffer: ReadBuffer) -> PeerViewEntryIndex {
var timestamp: Int32 = 0
buffer.read(&timestamp, offset: 0, length: 4)
timestamp = Int32(bigEndian: timestamp)
var namespace: Int32 = 0
buffer.read(&namespace, offset: 0, length: 4)
namespace = Int32(bigEndian: namespace)
var id: Int32 = 0
buffer.read(&id, offset: 0, length: 4)
id = Int32(bigEndian: id)
var peerIdRepresentation: Int64 = 0
buffer.read(&peerIdRepresentation, offset: 0, length: 8)
peerIdRepresentation = Int64(bigEndian: peerIdRepresentation)
let peerId = PeerId(peerIdRepresentation)
return PeerViewEntryIndex(peerId: peerId, messageIndex: MessageIndex(id: MessageId(peerId:peerId, namespace: namespace, id: id), timestamp: timestamp))
}
func bufferForPeerViewEntryIndex(index: PeerViewEntryIndex) -> MemoryBuffer {
let buffer = WriteBuffer()
var timestamp = Int32(bigEndian: index.messageIndex.timestamp)
buffer.write(&timestamp, offset: 0, length: 4)
var namespace = Int32(bigEndian: index.messageIndex.id.namespace)
buffer.write(&namespace, offset: 0, length: 4)
var id = Int32(bigEndian: index.messageIndex.id.id)
buffer.write(&id, offset: 0, length: 4)
var peerIdRepresentation = Int64(bigEndian: index.peerId.toInt64())
buffer.write(&peerIdRepresentation, offset: 0, length: 8)
return buffer
}
func messageIdsGroupedByNamespace(ids: [MessageId]) -> [MessageId.Namespace : [MessageId]] {
var grouped: [MessageId.Namespace : [MessageId]] = [:]
for id in ids {
if grouped[id.namespace] != nil {
grouped[id.namespace]!.append(id)
} else {
grouped[id.namespace] = [id]
}
}
return grouped
}
func mediaIdsGroupedByNamespaceFromMediaArray(mediaArray: [Media]) -> [MediaId.Namespace : [MediaId]] {
var grouped: [MediaId.Namespace : [MediaId]] = [:]
var seenMediaIds = Set<MediaId>()
for media in mediaArray {
if let id = media.id {
if !seenMediaIds.contains(id) {
seenMediaIds.insert(id)
if grouped[id.namespace] != nil {
grouped[id.namespace]!.append(id)
} else {
grouped[id.namespace] = [id]
}
}
}
}
return grouped
}
func mediaIdsGroupedByNamespaceFromSet(ids: Set<MediaId>) -> [MediaId.Namespace : [MediaId]] {
var grouped: [MediaId.Namespace : [MediaId]] = [:]
for id in ids {
if let _ = grouped[id.namespace] {
grouped[id.namespace]!.append(id)
} else {
grouped[id.namespace] = [id]
}
}
return grouped
}
func mediaIdsGroupedByNamespaceFromDictionaryKeys<T>(dict: [MediaId : T]) -> [MediaId.Namespace : [MediaId]] {
var grouped: [MediaId.Namespace : [MediaId]] = [:]
for (id, _) in dict {
if grouped[id.namespace] != nil {
grouped[id.namespace]!.append(id)
} else {
grouped[id.namespace] = [id]
}
}
return grouped
}
func messagesGroupedByPeerId(messages: [Message]) -> [(PeerId, [Message])] {
var grouped: [(PeerId, [Message])] = []
for message in messages {
var i = 0
let count = grouped.count
var found = false
while i < count {
if grouped[i].0 == message.id.peerId {
grouped[i].1.append(message)
found = true
break
}
i++
}
if !found {
grouped.append((message.id.peerId, [message]))
}
}
return grouped
}
func messageIdsGroupedByPeerId(messageIds: [MessageId]) -> [PeerId : [MessageId]] {
var grouped: [PeerId : [MessageId]] = [:]
for id in messageIds {
if grouped[id.peerId] != nil {
grouped[id.peerId]!.append(id)
} else {
grouped[id.peerId] = [id]
}
}
return grouped
}
func blobForMediaIds(ids: [MediaId]) -> Blob {
let data = NSMutableData()
var version: Int8 = 1
data.appendBytes(&version, length: 1)
var count = Int32(ids.count)
data.appendBytes(&count, length:4)
for id in ids {
var mNamespace = id.namespace
var mId = id.id
data.appendBytes(&mNamespace, length: 4)
data.appendBytes(&mId, length: 8)
}
return Blob(data: data)
}
func mediaIdsForBlob(blob: Blob) -> [MediaId] {
var ids: [MediaId] = []
var offset: Int = 0
var version = 0
blob.data.getBytes(&version, range: NSMakeRange(offset, 1))
offset += 1
if version == 1 {
var count: Int32 = 0
blob.data.getBytes(&count, range: NSMakeRange(offset, 4))
offset += 4
var i = 0
while i < Int(count) {
var mNamespace: Int32 = 0
var mId: Int64 = 0
blob.data.getBytes(&mNamespace, range: NSMakeRange(offset, 4))
blob.data.getBytes(&mId, range: NSMakeRange(offset + 4, 8))
ids.append(MediaId(namespace: mNamespace, id: mId))
offset += 12
i++
}
}
return ids
}
func memoryBufferForMessageIds(ids: [MessageId]) -> MemoryBuffer {
let data = NSMutableData()
var version: Int8 = 1
data.appendBytes(&version, length: 1)
var count = Int32(ids.count)
data.appendBytes(&count, length:4)
for id in ids {
var mPeerNamespace = id.peerId.namespace
var mPeerId = id.peerId.id
var mNamespace = id.namespace
var mId = id.id
data.appendBytes(&mPeerNamespace, length: 4)
data.appendBytes(&mPeerId, length: 4)
data.appendBytes(&mNamespace, length: 4)
data.appendBytes(&mId, length: 4)
}
return MemoryBuffer(data: data)
}
func messageIdsForMemoryBuffer(buffer: MemoryBuffer) -> [MessageId] {
var ids: [MessageId] = []
let readBuffer = ReadBuffer(memoryBufferNoCopy: buffer)
var count: Int32 = 0
readBuffer.read(&count, offset: 0, length: 4)
var i = 0
while i < Int(count) {
var mPeerNamespace: Int32 = 0
var mPeerId: Int32 = 0
var mNamespace: Int32 = 0
var mId: Int32 = 0
readBuffer.read(&mPeerNamespace, offset: 0, length: 4)
readBuffer.read(&mPeerId, offset: 0, length: 4)
readBuffer.read(&mNamespace, offset: 0, length: 4)
readBuffer.read(&mId, offset: 0, length: 4)
ids.append(MessageId(peerId: PeerId(namespace: mPeerNamespace, id: mPeerId), namespace: mNamespace, id: mId))
i++
}
return ids
}

View File

@@ -3,5 +3,6 @@ module sqlcipher {
header "sqlcipher/sqlite3ext.h"
header "sqlcipher/SQLite-Bridging.h"
header "sqlcipher/fts3_tokenizer.h"
header "../../submodules/lmdb/libraries/liblmdb/lmdb.h"
export *
}

274
Postbox/PostboxTables.swift Normal file
View File

@@ -0,0 +1,274 @@
import Foundation
struct Table_Meta {
static let id: Int32 = 0
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4)
}
static func key(key: ValueBoxKey = Table_Meta.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: 0)
return key
}
}
struct Table_State {
static let id: Int32 = 1
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4)
}
static func key(key: ValueBoxKey = Table_State.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: 0)
return key
}
}
struct Table_Keychain {
static let id: Int32 = 2
static func key(string: String) -> ValueBoxKey {
let data = string.dataUsingEncoding(NSUTF8StringEncoding) ?? NSData()
let key = ValueBoxKey(length: data.length)
memcpy(key.memory, data.bytes, data.length)
return key
}
}
struct Table_Message {
static let id: Int32 = 4
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 8 + 4 + 4)
}
static func lowerBoundKey(peerId: PeerId, namespace: Int32) -> ValueBoxKey {
let key = ValueBoxKey(length: 8 + 4)
key.setInt64(0, value: peerId.toInt64())
key.setInt32(8, value: namespace)
return key
}
static func upperBoundKey(peerId: PeerId, namespace: Int32) -> ValueBoxKey {
let key = ValueBoxKey(length: 8 + 4)
key.setInt64(0, value: peerId.toInt64())
key.setInt32(8, value: namespace)
return key.successor
}
static func key(messageId: MessageId, key: ValueBoxKey = Table_Message.emptyKey()) -> ValueBoxKey {
key.setInt64(0, value: messageId.peerId.toInt64())
key.setInt32(8, value: messageId.namespace)
key.setInt32(8 + 4, value: messageId.id)
return key
}
static func set(message: Message, encoder: Encoder = Encoder()) -> MemoryBuffer {
encoder.reset()
encoder.encodeRootObject(message)
return encoder.memoryBuffer()
}
static func get(value: ReadBuffer) -> Message? {
if let message = Decoder(buffer: value).decodeRootObject() as? Message {
return message
}
return nil
}
}
struct Table_AbsoluteMessageId {
static let id: Int32 = 5
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4)
}
static func key(id: Int32, key: ValueBoxKey = Table_AbsoluteMessageId.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: id)
return key
}
static func set(messageId: MessageId) -> MemoryBuffer {
let buffer = WriteBuffer()
var peerId: Int64 = messageId.peerId.toInt64()
buffer.write(&peerId, offset: 0, length: 8)
var id_namespace: Int32 = messageId.namespace
buffer.write(&id_namespace, offset: 0, length: 4)
return buffer
}
static func get(id: Int32, value: ReadBuffer) -> MessageId {
let offset = value.offset
var peerId: Int64 = 0
var id_namespace: Int32 = 0
value.read(&peerId, offset: 0, length: 8)
value.read(&id_namespace, offset: 0, length: 4)
value.offset = offset
return MessageId(peerId: PeerId(peerId), namespace: id_namespace, id: id)
}
}
struct Table_Media {
static let id: Int32 = 6
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4 + 8)
}
static func key(id: MediaId, key: ValueBoxKey = Table_Media.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: id.namespace)
key.setInt64(4, value: id.id)
return key
}
static func set(media: Media, encoder: Encoder = Encoder()) -> MemoryBuffer {
encoder.reset()
encoder.encodeRootObject(media)
return encoder.memoryBuffer()
}
static func get(value: ReadBuffer) -> Media? {
let decoder = Decoder(buffer: value)
if let media = decoder.decodeRootObject() as? Media {
return media
}
return nil
}
}
struct Table_Media_MessageIds {
static let id: Int32 = 3
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4 + 8 + 8 + 4 + 4)
}
static func key(id: MediaId, messageId: MessageId, key: ValueBoxKey = Table_Media_MessageIds.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: id.namespace)
key.setInt64(4, value: id.id)
key.setInt64(4 + 8, value: messageId.peerId.toInt64())
key.setInt32(4 + 8 + 8, value: messageId.namespace)
key.setInt32(4 + 8 + 8 + 4, value: messageId.id)
return key
}
static func lowerBoundKey(id: MediaId) -> ValueBoxKey {
let key = ValueBoxKey(length: 4 + 8)
key.setInt32(0, value: id.namespace)
key.setInt64(4, value: id.id)
return key
}
static func upperBoundKey(id: MediaId) -> ValueBoxKey {
let key = ValueBoxKey(length: 4 + 8)
key.setInt32(0, value: id.namespace)
key.setInt64(4, value: id.id)
return key.successor
}
static func getMessageId(key: ValueBoxKey) -> MessageId {
let peerId = key.getInt64(4 + 8)
let messageId_namespace: Int32 = key.getInt32(4 + 8 + 8)
let messageId_id: Int32 = key.getInt32(4 + 8 + 8 + 4)
return MessageId(peerId: PeerId(peerId), namespace: messageId_namespace, id: messageId_id)
}
}
struct Table_Peer {
static let id: Int32 = 7
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4 + 4)
}
static func key(id: PeerId, key: ValueBoxKey = Table_Peer.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: id.namespace)
key.setInt32(4, value: id.id)
return key
}
}
struct Table_PeerEntry_Sorted {
static let id: Int32 = 8
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 4 + 4 + 4 + 8)
}
static func lowerBoundKey() -> ValueBoxKey {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: 0)
return key
}
static func upperBoundKey() -> ValueBoxKey {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: Int32.max)
return key
}
static func key(index: PeerViewEntryIndex, key: ValueBoxKey = Table_PeerEntry_Sorted.emptyKey()) -> ValueBoxKey {
key.setInt32(0, value: index.messageIndex.timestamp)
key.setInt32(4, value: index.messageIndex.id.namespace)
key.setInt32(4 + 4, value: index.messageIndex.id.id)
key.setInt64(4 + 4 + 4, value: index.peerId.toInt64())
return key
}
static func get(key: ValueBoxKey) -> PeerViewEntryIndex {
let messageIndex_timestamp = key.getInt32(0)
let messageIndex_id_namespace = key.getInt32(4)
let messageIndex_id_id = key.getInt32(4 + 4)
let messageIndex_peerId = key.getInt64(4 + 4 + 4)
return PeerViewEntryIndex(peerId: PeerId(messageIndex_peerId), messageIndex: MessageIndex(id: MessageId(peerId: PeerId(messageIndex_peerId), namespace: messageIndex_id_namespace, id: messageIndex_id_id), timestamp: messageIndex_timestamp))
}
}
struct Table_PeerEntry {
static let id: Int32 = 9
static func emptyKey() -> ValueBoxKey {
return ValueBoxKey(length: 8)
}
static func key(peerId: PeerId, key: ValueBoxKey = Table_PeerEntry.emptyKey()) -> ValueBoxKey {
key.setInt64(0, value: peerId.toInt64())
return key
}
static func set(index: PeerViewEntryIndex) -> MemoryBuffer {
let buffer = WriteBuffer()
var messageId_namespace: Int32 = index.messageIndex.id.namespace
var messageId_id: Int32 = index.messageIndex.id.id
var timestamp: Int32 = index.messageIndex.timestamp
buffer.write(&messageId_namespace, offset: 0, length: 4)
buffer.write(&messageId_id, offset: 0, length: 4)
buffer.write(&timestamp, offset: 0, length: 4)
return buffer
}
static func get(peerId: PeerId, value: ReadBuffer) -> PeerViewEntryIndex {
let offset = value.offset
var messageId_namespace: Int32 = 0
var messageId_id: Int32 = 0
var timestamp: Int32 = 0
value.read(&messageId_namespace, offset: 0, length: 4)
value.read(&messageId_id, offset: 0, length: 4)
value.read(&timestamp, offset: 0, length: 4)
let index = PeerViewEntryIndex(peerId: peerId, messageIndex: MessageIndex(id: MessageId(peerId: peerId, namespace: messageId_namespace, id: messageId_id), timestamp: timestamp))
value.offset = offset
return index
}
}

View File

@@ -0,0 +1,574 @@
import Foundation
import sqlcipher
private struct SqlitePreparedStatement {
let statement: COpaquePointer
func bind(index: Int, data: UnsafePointer<Void>, length: Int) {
sqlite3_bind_blob(statement, Int32(index), data, Int32(length), nil)
}
func bind(index: Int, number: Int32) {
sqlite3_bind_int(statement, Int32(index), number)
}
func reset() {
sqlite3_reset(statement)
sqlite3_clear_bindings(statement)
}
func step() -> Bool {
let result = sqlite3_step(statement)
if result != SQLITE_ROW && result != SQLITE_DONE {
assertionFailure("Sqlite error \(result)")
}
return result == SQLITE_ROW
}
func valueAt(index: Int) -> ReadBuffer {
let valueLength = sqlite3_column_bytes(statement, Int32(index))
let valueData = sqlite3_column_blob(statement, Int32(index))
let valueMemory = malloc(Int(valueLength))
memcpy(valueMemory, valueData, Int(valueLength))
return ReadBuffer(memory: valueMemory, length: Int(valueLength), freeWhenDone: true)
}
func keyAt(index: Int) -> ValueBoxKey {
let valueLength = sqlite3_column_bytes(statement, Int32(index))
let valueData = sqlite3_column_blob(statement, Int32(index))
let key = ValueBoxKey(length: Int(valueLength))
memcpy(key.memory, valueData, Int(valueLength))
return key
}
func destroy() {
sqlite3_finalize(statement)
}
}
public final class SqliteValueBox: ValueBox {
private let database: Database
private var tables = Set<Int32>()
private var getStatements: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeKeyDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueAscStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueAscStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueDescStatementsLimit: [Int32 : SqlitePreparedStatement] = [:]
private var rangeValueDescStatementsNoLimit: [Int32 : SqlitePreparedStatement] = [:]
private var existsStatements: [Int32 : SqlitePreparedStatement] = [:]
private var updateStatements: [Int32 : SqlitePreparedStatement] = [:]
private var insertStatements: [Int32 : SqlitePreparedStatement] = [:]
private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:]
public init(basePath: String) {
do {
try NSFileManager.defaultManager().createDirectoryAtPath(basePath, withIntermediateDirectories: true, attributes: nil)
} catch _ { }
let path = basePath + "/db_sqlite"
self.database = Database(path)
self.database.adjustChunkSize()
self.database.execute("PRAGMA page_size=1024")
self.database.execute("PRAGMA cache_size=-2097152")
self.database.execute("PRAGMA synchronous=NORMAL")
self.database.execute("PRAGMA journal_mode=truncate")
self.database.execute("PRAGMA temp_store=MEMORY")
//self.database.execute("PRAGMA wal_autocheckpoint=32")
//self.database.execute("PRAGMA journal_size_limit=1536")
let result = self.database.scalar("PRAGMA user_version") as! Int64
if result != 1 {
self.database.execute("PRAGMA user_version=1")
self.database.execute("CREATE TABLE __meta_tables (name INTEGER)")
}
for row in self.database.prepare("SELECT name FROM __meta_tables").run() {
self.tables.insert(Int32(row[0] as! Int64))
}
}
public func begin() {
self.database.transaction()
}
public func commit() {
self.database.commit()
}
private func getStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.getStatements[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT value FROM t\(table) WHERE key=?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.getStatements[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: key.memory, length: key.length)
return resultStatement
}
private func rangeKeyAscStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeKeyAscStatementsLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeKeyAscStatementsLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
resultStatement.bind(3, number: Int32(limit))
return resultStatement
}
private func rangeKeyAscStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeKeyAscStatementsNoLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeKeyAscStatementsNoLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
return resultStatement
}
private func rangeKeyDescStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeKeyDescStatementsLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeKeyDescStatementsLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
resultStatement.bind(3, number: Int32(limit))
return resultStatement
}
private func rangeKeyDescStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeKeyDescStatementsNoLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeKeyDescStatementsNoLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
return resultStatement
}
private func rangeValueAscStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeValueAscStatementsLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC LIMIT ?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeValueAscStatementsLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
resultStatement.bind(3, number: Int32(limit))
return resultStatement
}
private func rangeValueAscStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeValueAscStatementsNoLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key ASC", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeValueAscStatementsNoLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
return resultStatement
}
private func rangeValueDescStatementLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey, limit: Int) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeValueDescStatementsLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC LIMIT ?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeValueDescStatementsLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
resultStatement.bind(3, number: Int32(limit))
return resultStatement
}
private func rangeValueDescStatementNoLimit(table: Int32, start: ValueBoxKey, end: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.rangeKeyDescStatementsNoLimit[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key, value FROM t\(table) WHERE key > ? AND key < ? ORDER BY key DESC", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.rangeValueDescStatementsNoLimit[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: start.memory, length: start.length)
resultStatement.bind(2, data: end.memory, length: end.length)
return resultStatement
}
private func existsStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.existsStatements[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "SELECT key FROM t\(table) WHERE key=?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.existsStatements[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: key.memory, length: key.length)
return resultStatement
}
private func updateStatement(table: Int32, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.updateStatements[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "UPDATE t\(table) SET value=? WHERE key=?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.updateStatements[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: value.memory, length: value.length)
resultStatement.bind(2, data: key.memory, length: key.length)
return resultStatement
}
private func insertStatement(table: Int32, key: ValueBoxKey, value: MemoryBuffer) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.insertStatements[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "INSERT INTO t\(table) (key, value) VALUES(?, ?)", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.insertStatements[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: key.memory, length: key.length)
resultStatement.bind(2, data: value.memory, length: value.length)
return resultStatement
}
private func deleteStatement(table: Int32, key: ValueBoxKey) -> SqlitePreparedStatement {
let resultStatement: SqlitePreparedStatement
if let statement = self.deleteStatements[table] {
resultStatement = statement
} else {
var statement: COpaquePointer = nil
sqlite3_prepare_v2(self.database.handle, "DELETE FROM t\(table) WHERE key=?", -1, &statement, nil)
let preparedStatement = SqlitePreparedStatement(statement: statement)
self.deleteStatements[table] = preparedStatement
resultStatement = preparedStatement
}
resultStatement.reset()
resultStatement.bind(1, data: key.memory, length: key.length)
return resultStatement
}
public func get(table: Int32, key: ValueBoxKey) -> ReadBuffer? {
if self.tables.contains(table) {
let statement = self.getStatement(table, key: key)
var buffer: ReadBuffer?
while statement.step() {
buffer = statement.valueAt(0)
break
}
statement.reset()
return buffer
}
return nil
}
public func exists(table: Int32, key: ValueBoxKey) -> Bool {
if let _ = self.get(table, key: key) {
return true
}
return false
}
public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, @noescape values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int) {
if start == end {
return
}
if self.tables.contains(table) {
let statement: SqlitePreparedStatement
if start < end {
if limit <= 0 {
statement = self.rangeValueAscStatementNoLimit(table, start: start, end: end)
} else {
statement = self.rangeValueAscStatementLimit(table, start: start, end: end, limit: limit)
}
} else {
if limit <= 0 {
statement = self.rangeValueDescStatementNoLimit(table, start: end, end: start)
} else {
statement = self.rangeValueDescStatementLimit(table, start: end, end: start, limit: limit)
}
}
while statement.step() {
let key = statement.keyAt(0)
let value = statement.valueAt(1)
if !values(key, value) {
break
}
}
statement.reset()
}
}
public func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, keys: ValueBoxKey -> Bool, limit: Int) {
if self.tables.contains(table) {
let statement: SqlitePreparedStatement
if start < end {
if limit <= 0 {
statement = self.rangeKeyAscStatementNoLimit(table, start: start, end: end)
} else {
statement = self.rangeKeyAscStatementLimit(table, start: start, end: end, limit: limit)
}
} else {
if limit <= 0 {
statement = self.rangeKeyDescStatementNoLimit(table, start: end, end: start)
} else {
statement = self.rangeKeyDescStatementLimit(table, start: end, end: start, limit: limit)
}
}
while statement.step() {
let key = statement.keyAt(0)
if !keys(key) {
break
}
}
statement.reset()
}
}
public func set(table: Int32, key: ValueBoxKey, value: MemoryBuffer) {
if !self.tables.contains(table) {
self.database.execute("CREATE TABLE t\(table) (key BLOB, value BLOB)")
self.database.execute("CREATE INDEX t\(table)_key ON t\(table) (key)")
self.tables.insert(table)
self.database.execute("INSERT INTO __meta_tables(name) VALUES (\(table))")
}
var exists = false
let existsStatement = self.existsStatement(table, key: key)
if existsStatement.step() {
exists = true
}
existsStatement.reset()
if exists {
let statement = self.updateStatement(table, key: key, value: value)
while statement.step() {
}
statement.reset()
} else {
let statement = self.insertStatement(table, key: key, value: value)
while statement.step() {
}
statement.reset()
}
}
public func remove(table: Int32, key: ValueBoxKey) {
if self.tables.contains(table) {
let statement = self.deleteStatement(table, key: key)
while statement.step() {
}
statement.reset()
}
}
public func drop() {
for (_, statement) in self.getStatements {
statement.destroy()
}
self.getStatements.removeAll()
for (_, statement) in self.rangeKeyAscStatementsLimit {
statement.destroy()
}
self.rangeKeyAscStatementsLimit.removeAll()
for (_, statement) in self.rangeKeyAscStatementsNoLimit {
statement.destroy()
}
self.rangeKeyAscStatementsNoLimit.removeAll()
for (_, statement) in self.rangeKeyDescStatementsLimit {
statement.destroy()
}
self.rangeKeyDescStatementsLimit.removeAll()
for (_, statement) in self.rangeKeyDescStatementsNoLimit {
statement.destroy()
}
self.rangeKeyDescStatementsNoLimit.removeAll()
for (_, statement) in self.rangeValueAscStatementsLimit {
statement.destroy()
}
self.rangeValueAscStatementsLimit.removeAll()
for (_, statement) in self.rangeValueAscStatementsNoLimit {
statement.destroy()
}
self.rangeValueAscStatementsNoLimit.removeAll()
for (_, statement) in self.rangeValueDescStatementsLimit {
statement.destroy()
}
self.rangeValueDescStatementsLimit.removeAll()
for (_, statement) in self.rangeValueDescStatementsNoLimit {
statement.destroy()
}
self.rangeValueDescStatementsNoLimit.removeAll()
for (_, statement) in self.existsStatements {
statement.destroy()
}
self.existsStatements.removeAll()
for (_, statement) in self.updateStatements {
statement.destroy()
}
self.updateStatements.removeAll()
for (_, statement) in self.insertStatements {
statement.destroy()
}
self.insertStatements.removeAll()
for (_, statement) in self.deleteStatements {
statement.destroy()
}
self.deleteStatements.removeAll()
for table in self.tables {
self.database.execute("DROP TABLE IF EXISTS t\(table)")
}
self.database.execute("DELETE FROM __meta_tables")
}
}

14
Postbox/ValueBox.swift Normal file
View File

@@ -0,0 +1,14 @@
import Foundation
public protocol ValueBox {
func begin()
func commit()
func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, @noescape values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int)
func range(table: Int32, start: ValueBoxKey, end: ValueBoxKey, keys: ValueBoxKey -> Bool, limit: Int)
func get(table: Int32, key: ValueBoxKey) -> ReadBuffer?
func exists(table: Int32, key: ValueBoxKey) -> Bool
func set(table: Int32, key: ValueBoxKey, value: MemoryBuffer)
func remove(table: Int32, key: ValueBoxKey)
func drop()
}

144
Postbox/ValueBoxKey.swift Normal file
View File

@@ -0,0 +1,144 @@
import Foundation
private final class ValueBoxKeyImpl {
let memory: UnsafeMutablePointer<Void>
init(memory: UnsafeMutablePointer<Void>) {
self.memory = memory
}
deinit {
free(self.memory)
}
}
public struct ValueBoxKey: Comparable, CustomStringConvertible {
public let memory: UnsafeMutablePointer<Void>
public let length: Int
private let impl: ValueBoxKeyImpl
public init(length: Int) {
self.memory = malloc(length)
self.length = length
self.impl = ValueBoxKeyImpl(memory: self.memory)
}
public init(_ value: String) {
let data = value.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
self.memory = malloc(data.length)
self.length = data.length
self.impl = ValueBoxKeyImpl(memory: self.memory)
memcpy(self.memory, data.bytes, data.length)
}
public init(_ buffer: MemoryBuffer) {
self.memory = malloc(buffer.length)
self.length = buffer.length
self.impl = ValueBoxKeyImpl(memory: self.memory)
memcpy(self.memory, buffer.memory, buffer.length)
}
public func setInt32(offset: Int, value: Int32) {
var bigEndianValue = Int32(bigEndian: value)
memcpy(self.memory + offset, &bigEndianValue, 4)
}
public func setInt64(offset: Int, value: Int64) {
var bigEndianValue = Int64(bigEndian: value)
memcpy(self.memory + offset, &bigEndianValue, 8)
}
public func getInt32(offset: Int) -> Int32 {
var value: Int32 = 0
memcpy(&value, self.memory + offset, 4)
return Int32(bigEndian: value)
}
public func getInt64(offset: Int) -> Int64 {
var value: Int64 = 0
memcpy(&value, self.memory + offset, 8)
return Int64(bigEndian: value)
}
public func prefix(length: Int) -> ValueBoxKey {
assert(length <= self.length, "length <= self.length")
let key = ValueBoxKey(length: length)
memcpy(key.memory, self.memory, length)
return key
}
public var successor: ValueBoxKey {
let key = ValueBoxKey(length: self.length)
memcpy(key.memory, self.memory, self.length)
let memory = UnsafeMutablePointer<UInt8>(key.memory)
var i = self.length - 1
while i >= 0 {
var byte = memory[i]
if byte != 0xff {
byte += 1
memory[i] = byte
break
} else {
byte = 0
memory[i] = byte
}
i--
}
return key
}
public var predecessor: ValueBoxKey {
let key = ValueBoxKey(length: self.length)
memcpy(key.memory, self.memory, self.length)
let memory = UnsafeMutablePointer<UInt8>(key.memory)
var i = self.length - 1
while i >= 0 {
var byte = memory[i]
if byte != 0x00 {
byte -= 1
memory[i] = byte
break
} else {
byte = 0xff
memory[i] = byte
}
i--
}
return key
}
public var description: String {
let string = NSMutableString()
let memory = UnsafeMutablePointer<UInt8>(self.memory)
for i in 0 ..< self.length {
let byte: Int = Int(memory[i])
string.appendFormat("%02x", byte)
}
return string as String
}
}
public func ==(lhs: ValueBoxKey, rhs: ValueBoxKey) -> Bool {
return lhs.length == rhs.length && memcmp(lhs.memory, rhs.memory, lhs.length) == 0
}
private func mdb_cmp_memn(a_memory: UnsafeMutablePointer<Void>, _ a_length: Int, _ b_memory: UnsafeMutablePointer<Void>, _ b_length: Int) -> Int
{
var diff: Int = 0
var len_diff: Int = 0
var len: Int = 0
len = a_length
len_diff = a_length - b_length
if len_diff > 0 {
len = b_length
len_diff = 1
}
diff = Int(memcmp(a_memory, b_memory, len))
return diff != 0 ? diff : len_diff < 0 ? -1 : len_diff
}
public func <(lhs: ValueBoxKey, rhs: ValueBoxKey) -> Bool {
return mdb_cmp_memn(lhs.memory, lhs.length, rhs.memory, rhs.length) < 0
}

View File

@@ -97,6 +97,14 @@ func ==(lhs: TestKey, rhs: TestKey) -> Bool {
return lhs.value == rhs.value
}
class EmptyState: PostboxState {
required init(decoder: Decoder) {
}
func encode(encoder: Encoder) {
}
}
class SerializationTests: XCTestCase {
override func setUp() {
super.setUp()
@@ -107,9 +115,9 @@ class SerializationTests: XCTestCase {
}
func testExample() {
declareEncodable(TestParent.self, { TestParent(decoder: $0) })
declareEncodable(TestObject.self, { TestObject(decoder: $0) })
declareEncodable(TestKey.self, { TestKey(decoder: $0) })
declareEncodable(TestParent.self, f: { TestParent(decoder: $0) })
declareEncodable(TestObject.self, f: { TestObject(decoder: $0) })
declareEncodable(TestKey.self, f: { TestKey(decoder: $0) })
let encoder = Encoder()
encoder.encodeInt32(12345, forKey: "a")
@@ -156,4 +164,98 @@ class SerializationTests: XCTestCase {
XCTAssert(decoder.decodeInt64ForKey("b") == Int64(12345), "int64 failed")
XCTAssert(decoder.decodeInt32ForKey("a") == 12345, "int32 failed")
}
func testIndexBaselinePerformance() {
let basePath = "/tmp/postboxtest"
do {
try NSFileManager.defaultManager().removeItemAtPath(basePath)
} catch _ { }
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [], absoluteIndexedMessageNamespaces: [])
postbox._prepareBaselineIndexPerformance()
measureBlock {
postbox._measureBaselineIndexPerformance()
}
}
func testBlobReadPerformance() {
let basePath = "/tmp/postboxtest"
do {
try NSFileManager.defaultManager().removeItemAtPath(basePath)
} catch _ { }
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [], absoluteIndexedMessageNamespaces: [])
postbox._prepareBlobIndexPerformance()
measureBlock {
postbox._measureBlobReadPerformance()
}
}
func testBlobIndexPerformance() {
let basePath = "/tmp/postboxtest"
do {
try NSFileManager.defaultManager().removeItemAtPath(basePath)
} catch _ { }
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [], absoluteIndexedMessageNamespaces: [])
postbox._prepareBlobIndexPerformance()
measureBlock {
postbox._measureBlobIndexPerformance()
}
}
func testKeys() {
let key1 = ValueBoxKey(length: 8)
let key2 = ValueBoxKey(length: 8)
key1.setInt32(0, value: 1)
key1.setInt32(4, value: 2)
key2.setInt32(0, value: 1)
key2.setInt32(4, value: 3)
let lowerBound = ValueBoxKey(length: 4)
lowerBound.setInt32(0, value: 0)
let upperBound = ValueBoxKey(length: 4)
upperBound.setInt32(0, value: 2)
XCTAssert(key1 > lowerBound, "key1 <= lowerBound")
XCTAssert(key1 < upperBound, "key1 >= upperBound")
XCTAssert(key2 > lowerBound, "key2 <= lowerBound")
XCTAssert(key2 < upperBound, "key2 >= upperBound")
XCTAssert(key1 < key2, "key1 >= key2")
XCTAssert(key1.successor == key2, "key1.next != key2")
XCTAssert(key2.predecessor == key1, "key2.previous != key1")
}
func testKeyValue() {
let basePath = "/tmp/postboxtest"
do {
try NSFileManager.defaultManager().removeItemAtPath(basePath)
} catch _ { }
let box = SqliteValueBox(basePath: basePath)
box.transaction { transaction in
let key = ValueBoxKey(length: 4)
let value = WriteBuffer()
for i in 1 ... 100 {
key.setInt32(0, value: Int32(i))
transaction.set("test", key: key, value: value)
}
}
do {
box.transaction { transaction in
let lowerBound = ValueBoxKey(length: 4)
lowerBound.setInt32(0, value: 2)
let upperBound = ValueBoxKey(length: 4)
upperBound.setInt32(0, value: 99)
transaction.range("test", start: upperBound, end: lowerBound, values: { key, value in
print("\(key.getInt32(0))")
return true
}, limit: 10)
}
}
}
}

View File

@@ -1,826 +0,0 @@
import UIKit
import XCTest
import Postbox
enum TestPeerNamespace: PeerId.Namespace {
case User = 0
}
enum TestMessageNamespace: MessageId.Namespace {
case Cloud = 0
case Local = 1
}
enum TestMediaNamespace: MediaId.Namespace {
case Test = 0
}
class TestPeer: Peer {
var id: PeerId
init(id: PeerId) {
self.id = id
}
required init(decoder: Decoder) {
self.id = PeerId(decoder.decodeInt64ForKey("id"))
}
func encode(encoder: Encoder) {
encoder.encodeInt64(self.id.toInt64(), forKey: "id")
}
}
class TestMessage: Message {
var id: MessageId
var authorId: PeerId
var date: Int32
var text: String
var referencedMediaIds: [MediaId]
var timestamp: Int32 {
return date
}
init(id: MessageId, authorId: PeerId, date: Int32, text: String, referencedMediaIds: [MediaId]) {
self.id = id
self.authorId = authorId
self.date = date
self.text = text
self.referencedMediaIds = referencedMediaIds
}
required init(decoder: Decoder) {
self.id = MessageId(decoder.decodeBytesForKeyNoCopy("id"))
self.authorId = PeerId(decoder.decodeInt64ForKey("authorId"))
self.date = decoder.decodeInt32ForKey("date")
self.text = decoder.decodeStringForKey("text")
self.referencedMediaIds = MediaId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("mediaIds"))
}
func encode(encoder: Encoder) {
let buffer = WriteBuffer()
self.id.encodeToBuffer(buffer)
encoder.encodeBytes(buffer, forKey: "id")
buffer.reset()
encoder.encodeInt64(self.authorId.toInt64(), forKey: "authorId")
encoder.encodeInt32(self.date, forKey: "date")
encoder.encodeString(self.text, forKey: "text")
MediaId.encodeArrayToBuffer(self.referencedMediaIds, buffer: buffer)
encoder.encodeBytes(buffer, forKey: "mediaIds")
buffer.reset()
}
}
class TestMedia: Media {
var id: MediaId
init(id: MediaId) {
self.id = id
}
required init(decoder: Decoder) {
self.id = MediaId(decoder.decodeBytesForKeyNoCopy("id"))
}
func encode(encoder: Encoder) {
let buffer = WriteBuffer()
self.id.encodeToBuffer(buffer)
encoder.encodeBytes(buffer, forKey: "id")
}
}
class EmptyState: PostboxState {
required init(decoder: Decoder) {
}
func encode(encoder: Encoder) {
}
}
class PostboxTests: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func testAddMessages() {
declareEncodable(TestMessage.self, { TestMessage(decoder: $0) })
declareEncodable(TestMedia.self, { TestMedia(decoder: $0) })
let ownerId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 1000)
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
let messageNamespace = TestMessageNamespace.Cloud.rawValue
let basePath = "/tmp/postboxtest"
NSFileManager.defaultManager().removeItemAtPath(basePath, error: nil)
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [messageNamespace])
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 0 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox._dumpTables()
(postbox.modify { state -> Void in
var messageIds: [MessageId] = []
for i in 0 ..< 5 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
messageIds.append(messageId)
}
state.deleteMessagesWithIds(messageIds)
}).start()
postbox._dumpTables();
(postbox.modify { state -> Void in
var messageIds: [MessageId] = []
for i in 0 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
messageIds.append(messageId)
}
state.deleteMessagesWithIds(messageIds)
}).start()
postbox._dumpTables();
}
func testMessageNamespaceViewAddTail() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [:], messages: [], later: [:])
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
func add(message: Message) {
view.add(message)
println("\(view)\n")
}
/*func assertIds(earlier: Int32?, ids: [Int32], later: Int32?) {
let otherMessages = ids.reverse().map({ id -> Message in
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(id))
let message = TestMessage(id: messageId, authorId: otherId, date: Int32(0), text: "", referencedMediaIds: [])
return message
})
let otherView = MessageNamespaceView(namespace: messageNamespace, messages: otherMessages, earlierId: earlier, laterId: later, count: 2)
XCTAssert(view == otherView, "\(view) != \(otherView)")
}*/
add(message(1, 90, 90))
add(message(2, 70, 70))
add(message(1, 70, 70))
add(message(1, 80, 80))
add(message(2, 100, 100))
add(message(1, 75, 75))
add(message(1, 60, 60))
}
func testMessageNamespaceViewAddMiddle1() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [1: message(1, 90, 90)], messages: [message(1, 100, 100), message(1, 120, 120), message(1, 140, 140)], later: [1: message(1, 200, 200)])
func add(message: Message) {
view.add(message)
println("\(view)\n")
}
add(message(2, 105, 105))
add(message(2, 150, 150))
add(message(2, 250, 250))
add(message(2, 180, 180))
}
func testMessageNamespaceRemoveTail() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
var messages: [Message] = []
func print(messages: [Message]) {
var string = ""
string += "["
var first = true
for message in messages {
if first {
first = false
} else {
string += ", "
}
string += "\(message.id.namespace): \(message.id.id)\(message.timestamp)"
}
string += "]"
println(string)
}
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [:], messages: [], later: [:])
func id(namespace: Int32, id: Int32) -> MessageId {
return MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
}
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
func add(message: Message) {
view.add(message)
println("\(view)")
messages.append(message)
messages.sort({MessageIndex($0) < MessageIndex($1)})
print(messages)
println()
}
func remove(ids: Set<MessageId>) -> MutableMessageView.RemoveContext {
let context = view.remove(ids)
println("\(view)")
messages = messages.filter { !ids.contains($0.id) }
print(messages)
return context
}
func fetchEarlier(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = messages.count - 1
while i >= 0 && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id < id!) {
filtered.append(messages[i])
}
i--
}
return filtered
}
func fetchLater(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = 0
while i < messages.count && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id > id!) {
filtered.append(messages[i])
}
i++
}
return filtered
}
func complete(context: MutableMessageView.RemoveContext) {
view.complete(context, fetchEarlier: fetchEarlier(messages), fetchLater: fetchLater(messages))
println("\(view)\n")
}
add(message(1, 90, 90))
add(message(2, 70, 70))
add(message(1, 70, 70))
add(message(1, 80, 80))
add(message(2, 100, 100))
add(message(1, 75, 75))
add(message(1, 60, 60))
println("remove 1:90, 2:100")
complete(remove([id(1, 90), id(2, 100)]))
println("remove 1:60, 2:70")
complete(remove([id(1, 60), id(2, 70)]))
println("remove 1:80, 2:100")
complete(remove([id(1, 80), id(2, 100)]))
}
func testMessageNamespaceRemoveAllInside() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
var messages: [Message] = []
func print(messages: [Message]) {
var string = ""
string += "["
var first = true
for message in messages {
if first {
first = false
} else {
string += ", "
}
string += "\(message.id.namespace): \(message.id.id)\(message.timestamp)"
}
string += "]"
println(string)
}
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [:], messages: [], later: [:])
func id(namespace: Int32, id: Int32) -> MessageId {
return MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
}
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
func add(message: Message) {
view.add(message)
println("\(view)")
messages.append(message)
messages.sort({MessageIndex($0) < MessageIndex($1)})
print(messages)
println()
}
func remove(ids: Set<MessageId>) -> MutableMessageView.RemoveContext {
let context = view.remove(ids)
println("\(view)")
messages = messages.filter { !ids.contains($0.id) }
print(messages)
return context
}
func fetchEarlier(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = messages.count - 1
while i >= 0 && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id < id!) {
filtered.append(messages[i])
}
i--
}
return filtered
}
func fetchLater(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = 0
while i < messages.count && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id > id!) {
filtered.append(messages[i])
}
i++
}
return filtered
}
func complete(context: MutableMessageView.RemoveContext) {
view.complete(context, fetchEarlier: fetchEarlier(messages), fetchLater: fetchLater(messages))
println("\(view)\n")
}
add(message(2, 10, 10))
add(message(2, 20, 20))
add(message(1, 90, 90))
add(message(2, 70, 70))
add(message(1, 70, 70))
add(message(1, 80, 80))
add(message(2, 100, 100))
add(message(1, 75, 75))
add(message(1, 60, 60))
println("remove 2:20, 1:80, 1:90, 2:100")
complete(remove([id(2, 20), id(1, 80), id(1, 90), id(2, 100)]))
}
func testMessageNamespaceRemoveMiddleSome() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
var messages: [Message] = []
func print(messages: [Message]) {
var string = ""
string += "["
var first = true
for message in messages {
if first {
first = false
} else {
string += ", "
}
string += "\(message.id.namespace): \(message.id.id)\(message.timestamp)"
}
string += "]"
println(string)
}
func id(namespace: Int32, id: Int32) -> MessageId {
return MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
}
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
messages = [message(2, 1, 1), message(2, 2, 2), message(1, 98, 98), message(1, 99, 99), message(1, 100, 100), message(2, 101, 101), message(1, 102, 102), message(1, 103, 103), message(1, 104, 104), message(1, 105, 105), message(2, 200, 200), message(2, 300, 300)]
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [1: message(1, 99, 99), 2: message(2, 2, 2)], messages: [message(1, 100, 100), message(2, 101, 101), message(1, 102, 102)], later: [1: message(1, 103, 103), 2: message(2, 200, 200)])
func add(message: Message) {
view.add(message)
println("\(view)")
messages.append(message)
messages.sort({MessageIndex($0) < MessageIndex($1)})
print(messages)
println()
}
func remove(ids: Set<MessageId>) -> MutableMessageView.RemoveContext {
let context = view.remove(ids)
println("\(view)")
messages = messages.filter { !ids.contains($0.id) }
print(messages)
return context
}
func fetchEarlier(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = messages.count - 1
while i >= 0 && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id < id!) {
filtered.append(messages[i])
}
i--
}
return filtered
}
func fetchLater(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = 0
while i < messages.count && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id > id!) {
filtered.append(messages[i])
}
i++
}
return filtered
}
func complete(context: MutableMessageView.RemoveContext) {
view.complete(context, fetchEarlier: fetchEarlier(messages), fetchLater: fetchLater(messages))
println("\(view)\n")
}
print(messages)
println("\(view)")
println("remove 1:100, 2:101")
complete(remove([id(1, 100), id(2, 101)]))
}
func testMessageNamespaceRemoveMiddleAllInside() {
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
var messages: [Message] = []
func print(messages: [Message]) {
var string = ""
string += "["
var first = true
for message in messages {
if first {
first = false
} else {
string += ", "
}
string += "\(message.id.namespace): \(message.id.id)\(message.timestamp)"
}
string += "]"
println(string)
}
func id(namespace: Int32, id: Int32) -> MessageId {
return MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
}
func message(namespace: Int32, id: Int32, timestamp: Int32) -> Message {
let messageId = MessageId(peerId: otherId, namespace: namespace, id: Int32(id))
return TestMessage(id: messageId, authorId: otherId, date: Int32(timestamp), text: "", referencedMediaIds: [])
}
messages = [message(2, 1, 1), message(2, 2, 2), message(1, 98, 98), message(1, 99, 99), message(1, 100, 100), message(2, 101, 101), message(1, 102, 102), message(1, 103, 103), message(1, 104, 104), message(1, 105, 105), message(2, 200, 200), message(2, 300, 300)]
let view = MutableMessageView(namespaces: [1, 2], count: 3, earlier: [1: message(1, 99, 99), 2: message(2, 2, 2)], messages: [message(1, 100, 100), message(2, 101, 101), message(1, 102, 102)], later: [1: message(1, 103, 103), 2: message(2, 200, 200)])
func add(message: Message) {
view.add(message)
println("\(view)")
messages.append(message)
messages.sort({MessageIndex($0) < MessageIndex($1)})
print(messages)
println()
}
func remove(ids: Set<MessageId>) -> MutableMessageView.RemoveContext {
let context = view.remove(ids)
println("\(view)")
messages = messages.filter { !ids.contains($0.id) }
print(messages)
return context
}
func fetchEarlier(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = messages.count - 1
while i >= 0 && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id < id!) {
filtered.append(messages[i])
}
i--
}
return filtered
}
func fetchLater(messages: [Message])(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] {
var filtered: [Message] = []
var i = 0
while i < messages.count && filtered.count < count {
if messages[i].id.namespace == namespace && (id == nil || messages[i].id.id > id!) {
filtered.append(messages[i])
}
i++
}
return filtered
}
func complete(context: MutableMessageView.RemoveContext) {
view.complete(context, fetchEarlier: fetchEarlier(messages), fetchLater: fetchLater(messages))
println("\(view)\n")
}
print(messages)
println("\(view)\n")
println("remove 1:100, 2:101, 1: 102")
complete(remove([id(1, 100), id(2, 101), id(1, 102)]))
}
func testViewTail() {
declareEncodable(TestMessage.self, { TestMessage(decoder: $0) })
declareEncodable(TestMedia.self, { TestMedia(decoder: $0) })
let ownerId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 1000)
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
let messageNamespace = TestMessageNamespace.Cloud.rawValue
let basePath = "/tmp/postboxtest"
NSFileManager.defaultManager().removeItemAtPath(basePath, error: nil)
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [messageNamespace])
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 0 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox.tailMessageViewForPeerId(otherId, count: 4).start(next: { next in
println(next)
})
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 10 ..< 15 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox._sync()
}
func testViewAround() {
declareEncodable(TestMessage.self, { TestMessage(decoder: $0) })
declareEncodable(TestMedia.self, { TestMedia(decoder: $0) })
let ownerId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 1000)
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
let messageNamespaceCloud = TestMessageNamespace.Cloud.rawValue
let messageNamespaceLocal = TestMessageNamespace.Local.rawValue
let basePath = "/tmp/postboxtest"
NSFileManager.defaultManager().removeItemAtPath(basePath, error: nil)
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [messageNamespaceCloud, messageNamespaceLocal])
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 0 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespaceCloud, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
for i in 10 ..< 12 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespaceLocal, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
var i = 1000
postbox.aroundMessageViewForPeerId(otherId, id: MessageId(peerId: otherId, namespace: messageNamespaceCloud, id: Int32(i + 1)), count: 3).start(next: { next in
println(next)
})
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 10 ..< 15 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespaceCloud, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: ownerId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox._sync()
}
func testPeerView() {
let view = MutablePeerView(count: 3, earlier: nil, entries: [], later: nil)
let messageNamespaceCloud = TestMessageNamespace.Cloud.rawValue
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
var entries: [PeerViewEntry] = []
func print(entries: [PeerViewEntry]) {
var string = ""
string += "["
var first = true
for entry in entries {
if first {
first = false
} else {
string += ", "
}
string += "(p \(entry.peerId.namespace):\(entry.peerId.id), m \(entry.message.id.namespace):\(entry.message.id.id)\(entry.message.timestamp))"
}
string += "]"
println("\(string)")
}
func add(entry: PeerViewEntry) {
entries.append(entry)
entries.sort({ PeerViewEntryIndex($0) < PeerViewEntryIndex($1) })
view.addEntry(entry)
println("\n\(view)")
print(entries)
}
func remove(peerId: PeerId, context: MutablePeerView.RemoveContext? = nil) -> MutablePeerView.RemoveContext {
entries = entries.filter({ $0.peerId != peerId })
return view.removeEntry(context, peerId: peerId)
}
func fetchEarlier(entries: [PeerViewEntry])(index: PeerViewEntryIndex?, count: Int) -> [PeerViewEntry] {
var filtered: [PeerViewEntry] = []
var i = entries.count - 1
while i >= 0 && filtered.count < count {
if index == nil || PeerViewEntryIndex(entries[i]) < index! {
filtered.append(entries[i])
}
i--
}
return filtered
}
func fetchLater(entries: [PeerViewEntry])(index: PeerViewEntryIndex?, count: Int) -> [PeerViewEntry] {
var filtered: [PeerViewEntry] = []
var i = 0
while i < entries.count && filtered.count < count {
if index == nil || PeerViewEntryIndex(entries[i]) > index! {
filtered.append(entries[i])
}
i++
}
return filtered
}
func complete(context: MutablePeerView.RemoveContext) {
view.complete(context, fetchEarlier: fetchEarlier(entries), fetchLater: fetchLater(entries))
}
println("\(view)")
for i in 1 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespaceCloud, id: Int32(i * 2 * 100))
let message = TestMessage(id: messageId, authorId: otherId, date: Int32(i * 2 * 100), text: "\(i)", referencedMediaIds: [])
add(PeerViewEntry(peer: TestPeer(id: PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(i * 2))), message: message))
}
if true {
let i = 15
let messageId = MessageId(peerId: otherId, namespace: messageNamespaceCloud, id: Int32(i * 100))
let message = TestMessage(id: messageId, authorId: otherId, date: Int32(i * 100), text: "\(i)", referencedMediaIds: [])
add(PeerViewEntry(peer: TestPeer(id: PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(i))), message: message))
}
if true {
var context = remove(PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(15)))
context = remove(PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(14)), context: context)
context = remove(PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(16)), context: context)
context = remove(PeerId(namespace: TestPeerNamespace.User.rawValue, id: Int32(18)), context: context)
println("\n\(view)")
print(entries)
complete(context)
println("\(view)")
}
}
func testPeerViewTail() {
declareEncodable(TestMessage.self, { TestMessage(decoder: $0) })
declareEncodable(TestMedia.self, { TestMedia(decoder: $0) })
let otherId = PeerId(namespace: TestPeerNamespace.User.rawValue, id: 2000)
let messageNamespace = TestMessageNamespace.Cloud.rawValue
let basePath = "/tmp/postboxtest"
NSFileManager.defaultManager().removeItemAtPath(basePath, error: nil)
let postbox = Postbox<EmptyState>(basePath: basePath, messageNamespaces: [messageNamespace])
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 0 ..< 10 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: otherId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox.tailPeerView(3).start(next: { next in
println(next)
})
postbox.tailMessageViewForPeerId(otherId, count: 4).start(next: { next in
println(next)
})
(postbox.modify { state -> Void in
let testMedia = TestMedia(id: MediaId(namespace: TestMediaNamespace.Test.rawValue, id: 1))
for i in 10 ..< 15 {
let messageId = MessageId(peerId: otherId, namespace: messageNamespace, id: Int32(i + 1))
let message = TestMessage(id: messageId, authorId: otherId, date: Int32(i + 100), text: "\(i)", referencedMediaIds: [testMedia.id])
state.addMessages([message, message], medias: [testMedia])
}
return
}).start()
postbox._sync()
}
}

View File

@@ -0,0 +1,20 @@
mtest
mtest[23456]
testdb
mdb_copy
mdb_stat
*.[ao]
*.so
*.exe
*[~#]
*.bak
*.orig
*.rej
*.gcov
*.gcda
*.gcno
core
core.*
valgrind.*
man/
html/

View File

@@ -0,0 +1,112 @@
LMDB 0.9 Change Log
LMDB 0.9.14 Release (2014/09/20)
Fix to support 64K page size (ITS#7713)
Fix to persist decreased as well as increased mapsizes (ITS#7789)
Fix cursor bug when deleting last node of a DUPSORT key
Fix mdb_env_info to return FIXEDMAP address
Fix ambiguous error code from writing to closed DBI (ITS#7825)
Fix mdb_copy copying past end of file (ITS#7886)
Fix cursor bugs from page_merge/rebalance
Fix to dirty fewer pages in deletes (mdb_page_loose())
Fix mdb_dbi_open creating subDBs (ITS#7917)
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
Fix Windows compat issues in mtests (ITS#7879)
Add compacting variant of mdb_copy
Add BigEndian integer key compare code
Add mdb_dump/mdb_load utilities
LMDB 0.9.13 Release (2014/06/18)
Fix mdb_page_alloc unlimited overflow page search
Documentation
Re-fix MDB_CURRENT doc (ITS#7793)
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
LMDB 0.9.12 Release (2014/06/13)
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
Fix mdb_rebalance (ITS#7829)
Fix mdb_page_split (ITS#7815)
Fix md_entries count (ITS#7861,#7828,#7793)
Fix MDB_CURRENT (ITS#7793)
Fix possible crash on Windows DLL detach
Misc code cleanup
Documentation
mdb_cursor_put: cursor moves on error (ITS#7771)
LMDB 0.9.11 Release (2014/01/15)
Add mdb_env_set_assert() (ITS#7775)
Fix: invalidate txn on page allocation errors (ITS#7377)
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
Fix corruption from deletes (ITS#7756)
Fix Windows/MSVC build issues
Raise safe limit of max MDB_MAXKEYSIZE
Misc code cleanup
Documentation
Remove spurious note about non-overlapping flags (ITS#7665)
LMDB 0.9.10 Release (2013/11/12)
Add MDB_NOMEMINIT option
Fix mdb_page_split() again (ITS#7589)
Fix MDB_NORDAHEAD definition (ITS#7734)
Fix mdb_cursor_del() positioning (ITS#7733)
Partial fix for larger page sizes (ITS#7713)
Fix Windows64/MSVC build issues
LMDB 0.9.9 Release (2013/10/24)
Add mdb_env_get_fd()
Add MDB_NORDAHEAD option
Add MDB_NOLOCK option
Avoid wasting space in mdb_page_split() (ITS#7589)
Fix mdb_page_merge() cursor fixup (ITS#7722)
Fix mdb_cursor_del() on last delete (ITS#7718)
Fix adding WRITEMAP on existing env (ITS#7715)
Fix nested txns (ITS#7515)
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
Fix mdb_rebalance() cursor fixup (ITS#7701)
Misc code cleanup
Documentation
Note that by default, readers need write access
LMDB 0.9.8 Release (2013/09/09)
Allow mdb_env_set_mapsize() on an open environment
Fix mdb_dbi_flags() (ITS#7672)
Fix mdb_page_unspill() in nested txns
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
Tweak mdb_page_spill() to be less aggressive
Documentation
Update caveats since mdb_reader_check() added in 0.9.7
LMDB 0.9.7 Release (2013/08/17)
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
Fix mdb_page_split() ref beyond cursor depth
Fix read txn data race (ITS#7635)
Fix mdb_rebalance (ITS#7536, #7538)
Fix mdb_drop() (ITS#7561)
Misc DEBUG macro fixes
Add MDB_NOTLS envflag
Add mdb_env_copyfd()
Add mdb_txn_env() (ITS#7660)
Add mdb_dbi_flags() (ITS#7661)
Add mdb_env_get_maxkeysize()
Add mdb_env_reader_list()/mdb_env_reader_check()
Add mdb_page_spill/unspill, remove hard txn size limit
Use shorter names for semaphores (ITS#7615)
Build
Fix install target (ITS#7656)
Documentation
Misc updates for cursors, DB handles, data lifetime
LMDB 0.9.6 Release (2013/02/25)
Many fixes/enhancements
LMDB 0.9.5 Release (2012/11/30)
Renamed from libmdb to liblmdb
Many fixes/enhancements

View File

@@ -0,0 +1,20 @@
Copyright 2011-2014 Howard Chu, Symas Corp.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted only as authorized by the OpenLDAP
Public License.
A copy of this license is available in the file LICENSE in the
top-level directory of the distribution or, alternatively, at
<http://www.OpenLDAP.org/license.html>.
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
Individual files and/or contributed packages may be copyright by
other parties and/or subject to additional restrictions.
This work also contains materials derived from public sources.
Additional information about OpenLDAP can be obtained at
<http://www.openldap.org/>.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
The OpenLDAP Public License
Version 2.8, 17 August 2003
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions in source form must retain copyright statements
and notices,
2. Redistributions in binary form must reproduce applicable copyright
statements and notices, this list of conditions, and the following
disclaimer in the documentation and/or other materials provided
with the distribution, and
3. Redistributions must contain a verbatim copy of this document.
The OpenLDAP Foundation may revise this license from time to time.
Each revision is distinguished by a version number. You may use
this Software under terms of this license revision or under the
terms of any subsequent revision of the license.
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The names of the authors and copyright holders must not be used in
advertising or otherwise to promote the sale, use or other dealing
in this Software without specific, written prior permission. Title
to copyright in this Software shall at all times remain with copyright
holders.
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
California, USA. All Rights Reserved. Permission to copy and
distribute verbatim copies of this document is granted.

View File

@@ -0,0 +1,97 @@
# Makefile for liblmdb (Lightning memory-mapped database library).
########################################################################
# Configuration. The compiler options must enable threaded compilation.
#
# Preprocessor macros (for CPPFLAGS) of interest...
# Note that the defaults should already be correct for most
# platforms; you should not need to change any of these.
# Read their descriptions in mdb.c if you do:
#
# - MDB_USE_POSIX_SEM
# - MDB_DSYNC
# - MDB_FDATASYNC
# - MDB_USE_PWRITEV
#
# There may be other macros in mdb.c of interest. You should
# read mdb.c before changing any of them.
#
CC = gcc
W = -W -Wall -Wno-unused-parameter -Wbad-function-cast -Wuninitialized
THREADS = -pthread
OPT = -O2 -g
CFLAGS = $(THREADS) $(OPT) $(W) $(XCFLAGS)
LDLIBS =
SOLIBS =
prefix = /usr/local
########################################################################
IHDRS = lmdb.h
ILIBS = liblmdb.a liblmdb.so
IPROGS = mdb_stat mdb_copy mdb_dump mdb_load
IDOCS = mdb_stat.1 mdb_copy.1 mdb_dump.1 mdb_load.1
PROGS = $(IPROGS) mtest mtest2 mtest3 mtest4 mtest5
all: $(ILIBS) $(PROGS)
install: $(ILIBS) $(IPROGS) $(IHDRS)
for f in $(IPROGS); do cp $$f $(DESTDIR)$(prefix)/bin; done
for f in $(ILIBS); do cp $$f $(DESTDIR)$(prefix)/lib; done
for f in $(IHDRS); do cp $$f $(DESTDIR)$(prefix)/include; done
for f in $(IDOCS); do cp $$f $(DESTDIR)$(prefix)/man/man1; done
clean:
rm -rf $(PROGS) *.[ao] *.so *~ testdb
test: all
mkdir testdb
./mtest && ./mdb_stat testdb
liblmdb.a: mdb.o midl.o
ar rs $@ mdb.o midl.o
liblmdb.so: mdb.o midl.o
# $(CC) $(LDFLAGS) -pthread -shared -Wl,-Bsymbolic -o $@ mdb.o midl.o $(SOLIBS)
$(CC) $(LDFLAGS) -pthread -shared -o $@ mdb.o midl.o $(SOLIBS)
mdb_stat: mdb_stat.o liblmdb.a
mdb_copy: mdb_copy.o liblmdb.a
mdb_dump: mdb_dump.o liblmdb.a
mdb_load: mdb_load.o liblmdb.a
mtest: mtest.o liblmdb.a
mtest2: mtest2.o liblmdb.a
mtest3: mtest3.o liblmdb.a
mtest4: mtest4.o liblmdb.a
mtest5: mtest5.o liblmdb.a
mtest6: mtest6.o liblmdb.a
mdb.o: mdb.c lmdb.h midl.h
$(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -c mdb.c
midl.o: midl.c midl.h
$(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -c midl.c
%: %.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
%.o: %.c lmdb.h
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
COV_FLAGS=-fprofile-arcs -ftest-coverage
COV_OBJS=xmdb.o xmidl.o
coverage: xmtest
for i in mtest*.c [0-9]*.c; do j=`basename \$$i .c`; $(MAKE) $$j.o; \
gcc -o x$$j $$j.o $(COV_OBJS) -pthread $(COV_FLAGS); \
rm -rf testdb; mkdir testdb; ./x$$j; done
gcov xmdb.c
gcov xmidl.c
xmtest: mtest.o xmdb.o xmidl.o
gcc -o xmtest mtest.o xmdb.o xmidl.o -pthread $(COV_FLAGS)
xmdb.o: mdb.c lmdb.h midl.h
$(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -O0 $(COV_FLAGS) -c mdb.c -o $@
xmidl.o: midl.c midl.h
$(CC) $(CFLAGS) -fPIC $(CPPFLAGS) -O0 $(COV_FLAGS) -c midl.c -o $@

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
.TH MDB_COPY 1 "2014/06/20" "LMDB 0.9.14"
.\" Copyright 2012-2014 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_copy \- LMDB environment copy tool
.SH SYNOPSIS
.B mdb_copy
[\c
.BR \-V ]
[\c
.BR \-c ]
[\c
.BR \-n ]
.B srcpath
[\c
.BR dstpath ]
.SH DESCRIPTION
The
.B mdb_copy
utility copies an LMDB environment. The environment can
be copied regardless of whether it is currently in use.
No lockfile is created, since it gets recreated at need.
If
.I dstpath
is specified it must be the path of an empty directory
for storing the backup. Otherwise, the backup will be
written to stdout.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-c
Compact while copying. Only current data pages will be copied; freed
or unused pages will be omitted from the copy. This option will
slow down the backup process as it is more CPU-intensive.
.TP
.BR \-n
Open LDMB environment(s) which do not use subdirectories.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH CAVEATS
This utility can trigger significant file size growth if run
in parallel with write transactions, because pages which they
free during copying cannot be reused until the copy is done.
.SH "SEE ALSO"
.BR mdb_stat (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

View File

@@ -0,0 +1,82 @@
/* mdb_copy.c - memory-mapped database backup tool */
/*
* Copyright 2012 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#ifdef _WIN32
#include <windows.h>
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
#else
#define MDB_STDOUT 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "lmdb.h"
static void
sighandle(int sig)
{
}
int main(int argc,char * argv[])
{
int rc;
MDB_env *env;
const char *progname = argv[0], *act;
unsigned flags = MDB_RDONLY;
unsigned cpflags = 0;
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
if (argv[1][1] == 'n' && argv[1][2] == '\0')
flags |= MDB_NOSUBDIR;
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
cpflags |= MDB_CP_COMPACT;
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
printf("%s\n", MDB_VERSION_STRING);
exit(0);
} else
argc = 0;
}
if (argc<2 || argc>3) {
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
exit(EXIT_FAILURE);
}
#ifdef SIGPIPE
signal(SIGPIPE, sighandle);
#endif
#ifdef SIGHUP
signal(SIGHUP, sighandle);
#endif
signal(SIGINT, sighandle);
signal(SIGTERM, sighandle);
act = "opening environment";
rc = mdb_env_create(&env);
if (rc == MDB_SUCCESS) {
rc = mdb_env_open(env, argv[1], flags, 0664);
}
if (rc == MDB_SUCCESS) {
act = "copying";
if (argc == 2)
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
else
rc = mdb_env_copy2(env, argv[2], cpflags);
}
if (rc)
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
progname, act, rc, mdb_strerror(rc));
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -0,0 +1,75 @@
.TH MDB_DUMP 1 "2014/06/20" "LMDB 0.9.14"
.\" Copyright 2014 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_dump \- LMDB environment export tool
.SH SYNOPSIS
.B mdb_dump
.BR \ envpath
[\c
.BR \-V ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-l ]
[\c
.BR \-n ]
[\c
.BR \-p ]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.SH DESCRIPTION
The
.B mdb_dump
utility reads a database and writes its contents to the
standard output using a portable flat-text format
understood by the
.BR mdb_load (1)
utility.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-f \ file
Write to the specified file instead of to the standard output.
.TP
.BR \-l
List the databases stored in the environment. Just the
names will be listed, no data will be output.
.TP
.BR \-n
Dump an LMDB database which does not use subdirectories.
.TP
.BR \-p
If characters in either the key or data items are printing characters (as
defined by isprint(3)), output them directly. This option permits users to
use standard text editors and tools to modify the contents of databases.
Note: different systems may have different notions about what characters
are considered printing characters, and databases dumped in this manner may
be less portable to external systems.
.TP
.BR \-a
Dump all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
Dumping and reloading databases that use user-defined comparison functions
will result in new databases that use the default comparison functions.
\fBIn this case it is quite likely that the reloaded database will be
damaged beyond repair permitting neither record storage nor retrieval.\fP
The only available workaround is to modify the source for the
.BR mdb_load (1)
utility to load the database using the correct comparison functions.
.SH "SEE ALSO"
.BR mdb_load (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

View File

@@ -0,0 +1,317 @@
/* mdb_dump.c - memory-mapped database dump tool */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include "lmdb.h"
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
#define PRINT 1
static int mode;
typedef struct flagbit {
int bit;
char *name;
} flagbit;
flagbit dbflags[] = {
{ MDB_REVERSEKEY, "reversekey" },
{ MDB_DUPSORT, "dupsort" },
{ MDB_INTEGERKEY, "integerkey" },
{ MDB_DUPFIXED, "dupfixed" },
{ MDB_INTEGERDUP, "integerdup" },
{ MDB_REVERSEDUP, "reversedup" },
{ 0, NULL }
};
static volatile sig_atomic_t gotsig;
static void dumpsig( int sig )
{
gotsig=1;
}
static const char hexc[] = "0123456789abcdef";
static void hex(unsigned char c)
{
putchar(hexc[c >> 4]);
putchar(hexc[c & 0xf]);
}
static void text(MDB_val *v)
{
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
if (isprint(*c)) {
putchar(*c);
} else {
putchar('\\');
hex(*c);
}
c++;
}
putchar('\n');
}
static void byte(MDB_val *v)
{
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
hex(*c++);
}
putchar('\n');
}
/* Dump in BDB-compatible format */
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
{
MDB_cursor *mc;
MDB_stat ms;
MDB_val key, data;
MDB_envinfo info;
unsigned int flags;
int rc, i;
rc = mdb_dbi_flags(txn, dbi, &flags);
if (rc) return rc;
rc = mdb_stat(txn, dbi, &ms);
if (rc) return rc;
rc = mdb_env_info(mdb_txn_env(txn), &info);
if (rc) return rc;
printf("VERSION=3\n");
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
if (name)
printf("database=%s\n", name);
printf("type=btree\n");
printf("mapsize=%" Z "u\n", info.me_mapsize);
if (info.me_mapaddr)
printf("mapaddr=%p\n", info.me_mapaddr);
printf("maxreaders=%u\n", info.me_maxreaders);
if (flags & MDB_DUPSORT)
printf("duplicates=1\n");
for (i=0; dbflags[i].bit; i++)
if (flags & dbflags[i].bit)
printf("%s=1\n", dbflags[i].name);
printf("db_pagesize=%d\n", ms.ms_psize);
printf("HEADER=END\n");
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) return rc;
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT) == MDB_SUCCESS)) {
if (gotsig) {
rc = EINTR;
break;
}
if (mode & PRINT) {
text(&key);
text(&data);
} else {
byte(&key);
byte(&data);
}
}
printf("DATA=END\n");
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
return rc;
}
static void usage(char *prog)
{
fprintf(stderr, "usage: %s dbpath [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb]\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envflags = 0, list = 0;
if (argc < 2) {
usage(prog);
}
/* -a: dump main DB and all subDBs
* -s: dump only the named subDB
* -n: use NOSUBDIR flag on env_open
* -p: use printable characters
* -f: write to file instead of stdout
* -V: print version and exit
* (default) dump only the main DB
*/
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'l':
list = 1;
/*FALLTHROUGH*/;
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'f':
if (freopen(optarg, "w", stdout) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n",
prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'p':
mode |= PRINT;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
#ifdef SIGPIPE
signal(SIGPIPE, dumpsig);
#endif
#ifdef SIGHUP
signal(SIGHUP, dumpsig);
#endif
signal(SIGINT, dumpsig);
signal(SIGTERM, dumpsig);
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdb_env_set_maxdbs(env, 2);
}
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
int count = 0;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
count++;
str = malloc(key.mv_size+1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdb_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS) {
if (list) {
printf("%s\n", str);
list++;
} else {
rc = dumpit(txn, db2, str);
if (rc)
break;
}
mdb_close(env, db2);
}
free(str);
if (rc) continue;
}
mdb_cursor_close(cursor);
if (!count) {
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
rc = MDB_NOTFOUND;
} else if (rc == MDB_NOTFOUND) {
rc = MDB_SUCCESS;
}
} else {
rc = dumpit(txn, dbi, subname);
}
if (rc && rc != MDB_NOTFOUND)
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
mdb_close(env, dbi);
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -0,0 +1,77 @@
.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14"
.\" Copyright 2014 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_load \- LMDB environment import tool
.SH SYNOPSIS
.B mdb_load
.BR \ envpath
[\c
.BR \-V ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-n ]
[\c
.BI \-s \ subdb\fR]
[\c
.BR \-N ]
[\c
.BR \-T ]
.SH DESCRIPTION
The
.B mdb_load
utility reads from the standard input and loads it into the
LMDB environment
.BR envpath .
The input to
.B mdb_load
must be in the output format specified by the
.BR mdb_dump (1)
utility or as specified by the
.B -T
option below.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-f \ file
Read from the specified file instead of from the standard input.
.TP
.BR \-n
Load an LMDB database which does not use subdirectories.
.TP
.BR \-s \ subdb
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
.TP
.BR \-N
Don't overwrite existing records when loading into an already existing database; just skip them.
.TP
.BR \-T
Load data from simple text files. The input must be paired lines of text, where the first
line of the pair is the key item, and the second line of the pair is its corresponding
data item.
A simple escape mechanism, where newline and backslash (\\) characters are special, is
applied to the text input. Newline characters are interpreted as record separators.
Backslash characters in the text will be interpreted in one of two ways: If the backslash
character precedes another backslash character, the pair will be interpreted as a literal
backslash. If the backslash character precedes any other character, the two characters
following the backslash will be interpreted as a hexadecimal specification of a single
character; for example, \\0a is a newline character in the ASCII character set.
For this reason, any backslash or newline characters that naturally occur in the text
input must be escaped to avoid misinterpretation by
.BR mdb_load .
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdb_dump (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

View File

@@ -0,0 +1,452 @@
/* mdb_load.c - memory-mapped database load tool */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "lmdb.h"
#define PRINT 1
#define NOHDR 2
static int mode;
static char *subname = NULL;
static size_t lineno;
static int version;
static int flags;
static char *prog;
static int Eof;
static MDB_envinfo info;
static MDB_val kbuf, dbuf;
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
#define STRLENOF(s) (sizeof(s)-1)
typedef struct flagbit {
int bit;
char *name;
int len;
} flagbit;
#define S(s) s, STRLENOF(s)
flagbit dbflags[] = {
{ MDB_REVERSEKEY, S("reversekey") },
{ MDB_DUPSORT, S("dupsort") },
{ MDB_INTEGERKEY, S("integerkey") },
{ MDB_DUPFIXED, S("dupfixed") },
{ MDB_INTEGERDUP, S("integerdup") },
{ MDB_REVERSEDUP, S("reversedup") },
{ 0, NULL, 0 }
};
static void readhdr(void)
{
char *ptr;
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
lineno++;
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
if (version > 3) {
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
prog, lineno, version);
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
break;
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
mode |= PRINT;
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
if (subname) free(subname);
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr) *ptr = '\0';
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
if (i != 1) {
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
exit(EXIT_FAILURE);
}
} else {
int i;
for (i=0; dbflags[i].bit; i++) {
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
flags |= dbflags[i].bit;
break;
}
}
if (!dbflags[i].bit) {
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
if (!ptr) {
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
prog, lineno);
exit(EXIT_FAILURE);
} else {
*ptr = '\0';
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
prog, lineno, (char *)dbuf.mv_data);
}
}
}
}
}
static void badend(void)
{
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
prog, lineno);
}
static int unhex(unsigned char *c2)
{
int x, c;
x = *c2++ & 0x4f;
if (x & 0x40)
x -= 55;
c = x << 4;
x = *c2 & 0x4f;
if (x & 0x40)
x -= 55;
c |= x;
return c;
}
static int readline(MDB_val *out, MDB_val *buf)
{
unsigned char *c1, *c2, *end;
size_t len;
int c;
if (!(mode & NOHDR)) {
c = fgetc(stdin);
if (c == EOF) {
Eof = 1;
return EOF;
}
if (c != ' ') {
lineno++;
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
badend:
Eof = 1;
badend();
return EOF;
}
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
return EOF;
goto badend;
}
}
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
Eof = 1;
return EOF;
}
lineno++;
c1 = buf->mv_data;
len = strlen((char *)c1);
/* Is buffer too short? */
while (c1[len-1] != '\n') {
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
if (!buf->mv_data) {
Eof = 1;
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
prog, lineno);
return EOF;
}
c1 = buf->mv_data;
c1 += buf->mv_size;
if (fgets((char *)c1, buf->mv_size, stdin) == NULL) {
Eof = 1;
badend();
return EOF;
}
buf->mv_size *= 2;
len = strlen((char *)c1);
}
c1 = c2 = buf->mv_data;
len = strlen((char *)c1);
c1[--len] = '\0';
end = c1 + len;
if (mode & PRINT) {
while (c2 < end) {
if (*c2 == '\\') {
if (c2[1] == '\\') {
c1++; c2 += 2;
} else {
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(++c2);
c2 += 2;
}
} else {
c1++; c2++;
}
}
} else {
/* odd length not allowed */
if (len & 1) {
Eof = 1;
badend();
return EOF;
}
while (c2 < end) {
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(c2);
c2 += 2;
}
}
c2 = out->mv_data = buf->mv_data;
out->mv_size = c1 - c2;
return 0;
}
static void usage(void)
{
fprintf(stderr, "usage: %s dbpath [-V] [-f input] [-n] [-s name] [-N] [-T]\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_cursor *mc;
MDB_dbi dbi;
char *envname;
int envflags = 0, putflags = 0;
int dohdr = 0;
prog = argv[0];
if (argc < 2) {
usage();
}
/* -f: load file instead of stdin
* -n: use NOSUBDIR flag on env_open
* -s: load into named subDB
* -N: use NOOVERWRITE on puts
* -T: read plaintext
* -V: print version and exit
*/
while ((i = getopt(argc, argv, "f:ns:NTV")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'f':
if (freopen(optarg, "r", stdin) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n",
prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 's':
subname = strdup(optarg);
break;
case 'N':
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
break;
case 'T':
mode |= NOHDR;
break;
default:
usage();
}
}
if (optind != argc - 1)
usage();
dbuf.mv_size = 4096;
dbuf.mv_data = malloc(dbuf.mv_size);
if (!(mode & NOHDR))
readhdr();
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
mdb_env_set_maxdbs(env, 2);
if (info.me_maxreaders)
mdb_env_set_maxreaders(env, info.me_maxreaders);
if (info.me_mapsize)
mdb_env_set_mapsize(env, info.me_mapsize);
if (info.me_mapaddr)
envflags |= MDB_FIXEDMAP;
rc = mdb_env_open(env, envname, envflags, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
kbuf.mv_data = malloc(kbuf.mv_size);
while(!Eof) {
MDB_val key, data;
int batch = 0;
flags = 0;
if (!dohdr) {
dohdr = 1;
} else if (!(mode & NOHDR))
readhdr();
rc = mdb_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_open(txn, subname, flags|MDB_CREATE, &dbi);
if (rc) {
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while(1) {
rc = readline(&key, &kbuf);
if (rc == EOF)
break;
if (rc)
goto txn_abort;
rc = readline(&data, &dbuf);
if (rc)
goto txn_abort;
rc = mdb_cursor_put(mc, &key, &data, putflags);
if (rc == MDB_KEYEXIST && putflags)
continue;
if (rc)
goto txn_abort;
batch++;
if (batch == 100) {
rc = mdb_txn_commit(txn);
if (rc) {
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
prog, lineno, mdb_strerror(rc));
goto env_close;
}
rc = mdb_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
rc = mdb_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
batch = 0;
}
}
rc = mdb_txn_commit(txn);
txn = NULL;
if (rc) {
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
prog, lineno, mdb_strerror(rc));
goto env_close;
}
mdb_dbi_close(env, dbi);
}
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -0,0 +1,64 @@
.TH MDB_STAT 1 "2014/06/20" "LMDB 0.9.14"
.\" Copyright 2012-2014 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.SH NAME
mdb_stat \- LMDB environment status tool
.SH SYNOPSIS
.B mdb_stat
.BR \ envpath
[\c
.BR \-V ]
[\c
.BR \-e ]
[\c
.BR \-f [ f [ f ]]]
[\c
.BR \-n ]
[\c
.BR \-r [ r ]]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.SH DESCRIPTION
The
.B mdb_stat
utility displays the status of an LMDB environment.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-e
Display information about the database environment.
.TP
.BR \-f
Display information about the environment freelist.
If \fB\-ff\fP is given, summarize each freelist entry.
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
.TP
.BR \-n
Display the status of an LMDB database which does not use subdirectories.
.TP
.BR \-r
Display information about the environment reader table.
Shows the process ID, thread ID, and transaction ID for each active
reader slot. The process ID and transaction ID are in decimal, the
thread ID is in hexadecimal. The transaction ID is displayed as "-"
if the reader does not currently have a read transaction open.
If \fB\-rr\fP is given, check for stale entries in the reader
table and clear them. The reader table will be printed again
after the check is performed.
.TP
.BR \-a
Display the status of all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Display the status of a specific subdatabase.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdb_copy (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

View File

@@ -0,0 +1,263 @@
/* mdb_stat.c - memory-mapped database status tool */
/*
* Copyright 2011-2013 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lmdb.h"
#ifdef _WIN32
#define Z "I"
#else
#define Z "z"
#endif
static void prstat(MDB_stat *ms)
{
#if 0
printf(" Page size: %u\n", ms->ms_psize);
#endif
printf(" Tree depth: %u\n", ms->ms_depth);
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
printf(" Entries: %"Z"u\n", ms->ms_entries);
}
static void usage(char *prog)
{
fprintf(stderr, "usage: %s dbpath [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb]\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
MDB_stat mst;
MDB_envinfo mei;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
if (argc < 2) {
usage(prog);
}
/* -a: print stat of main DB and all subDBs
* -s: print stat of only the named subDB
* -e: print env info
* -f: print freelist info
* -r: print reader info
* -n: use NOSUBDIR flag on env_open
* -V: print version and exit
* (default) print stat of only the main DB
*/
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
switch(i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'e':
envinfo++;
break;
case 'f':
freinfo++;
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'r':
rdrinfo++;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
envname = argv[optind];
rc = mdb_env_create(&env);
if (rc) {
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdb_env_set_maxdbs(env, 4);
}
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
if (envinfo) {
(void)mdb_env_stat(env, &mst);
(void)mdb_env_info(env, &mei);
printf("Environment Info\n");
printf(" Map address: %p\n", mei.me_mapaddr);
printf(" Map size: %"Z"u\n", mei.me_mapsize);
printf(" Page size: %u\n", mst.ms_psize);
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
printf(" Max readers: %u\n", mei.me_maxreaders);
printf(" Number of readers used: %u\n", mei.me_numreaders);
}
if (rdrinfo) {
printf("Reader Table Status\n");
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
if (rdrinfo > 1) {
int dead;
mdb_reader_check(env, &dead);
printf(" %d stale readers cleared.\n", dead);
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
}
if (!(subname || alldbs || freinfo))
goto env_close;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
goto env_close;
}
if (freinfo) {
MDB_cursor *cursor;
MDB_val key, data;
size_t pages = 0, *iptr;
printf("Freelist Status\n");
dbi = 0;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
rc = mdb_stat(txn, dbi, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
prstat(&mst);
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
iptr = data.mv_data;
pages += *iptr;
if (freinfo > 1) {
char *bad = "";
size_t pg, prev;
ssize_t i, j, span = 0;
j = *iptr++;
for (i = j, prev = 1; --i >= 0; ) {
pg = iptr[i];
if (pg <= prev)
bad = " [bad sequence]";
prev = pg;
pg += span;
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
}
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
*(size_t *)key.mv_data, j, span, bad);
if (freinfo > 2) {
for (--j; j >= 0; ) {
pg = iptr[j];
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
pg, span);
}
}
}
}
mdb_cursor_close(cursor);
printf(" Free pages: %"Z"u\n", pages);
}
rc = mdb_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
rc = mdb_stat(txn, dbi, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
printf("Status of %s\n", subname ? subname : "Main DB");
prstat(&mst);
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
rc = mdb_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
str = malloc(key.mv_size+1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdb_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS)
printf("Status of %s\n", str);
free(str);
if (rc) continue;
rc = mdb_stat(txn, db2, &mst);
if (rc) {
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
goto txn_abort;
}
prstat(&mst);
mdb_close(env, db2);
}
mdb_cursor_close(cursor);
}
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
mdb_close(env, dbi);
txn_abort:
mdb_txn_abort(txn);
env_close:
mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -0,0 +1,360 @@
/** @file midl.c
* @brief ldap bdb back-end ID List functions */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2000-2014 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include "midl.h"
/** @defgroup internal LMDB Internals
* @{
*/
/** @defgroup idls ID List Management
* @{
*/
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
{
/*
* binary search of id in ids
* if found, returns position of id
* if not found, returns first position greater than id
*/
unsigned base = 0;
unsigned cursor = 1;
int val = 0;
unsigned n = ids[0];
while( 0 < n ) {
unsigned pivot = n >> 1;
cursor = base + pivot + 1;
val = CMP( ids[cursor], id );
if( val < 0 ) {
n = pivot;
} else if ( val > 0 ) {
base = cursor;
n -= pivot + 1;
} else {
return cursor;
}
}
if( val > 0 ) {
++cursor;
}
return cursor;
}
#if 0 /* superseded by append/sort */
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
{
unsigned x, i;
x = mdb_midl_search( ids, id );
assert( x > 0 );
if( x < 1 ) {
/* internal error */
return -2;
}
if ( x <= ids[0] && ids[x] == id ) {
/* duplicate */
assert(0);
return -1;
}
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
/* no room */
--ids[0];
return -2;
} else {
/* insert id */
for (i=ids[0]; i>x; i--)
ids[i] = ids[i-1];
ids[x] = id;
}
return 0;
}
#endif
MDB_IDL mdb_midl_alloc(int num)
{
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
if (ids) {
*ids++ = num;
*ids = 0;
}
return ids;
}
void mdb_midl_free(MDB_IDL ids)
{
if (ids)
free(ids-1);
}
int mdb_midl_shrink( MDB_IDL *idp )
{
MDB_IDL ids = *idp;
if (*(--ids) > MDB_IDL_UM_MAX &&
(ids = realloc(ids, (MDB_IDL_UM_MAX+1) * sizeof(MDB_ID))))
{
*ids++ = MDB_IDL_UM_MAX;
*idp = ids;
return 1;
}
return 0;
}
static int mdb_midl_grow( MDB_IDL *idp, int num )
{
MDB_IDL idn = *idp-1;
/* grow it */
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
if (!idn)
return ENOMEM;
*idn++ += num;
*idp = idn;
return 0;
}
int mdb_midl_need( MDB_IDL *idp, unsigned num )
{
MDB_IDL ids = *idp;
num += ids[0];
if (num > ids[-1]) {
num = (num + num/4 + (256 + 2)) & -256;
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
return ENOMEM;
*ids++ = num - 2;
*idp = ids;
}
return 0;
}
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
{
MDB_IDL ids = *idp;
/* Too big? */
if (ids[0] >= ids[-1]) {
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
return ENOMEM;
ids = *idp;
}
ids[0]++;
ids[ids[0]] = id;
return 0;
}
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
{
MDB_IDL ids = *idp;
/* Too big? */
if (ids[0] + app[0] >= ids[-1]) {
if (mdb_midl_grow(idp, app[0]))
return ENOMEM;
ids = *idp;
}
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
ids[0] += app[0];
return 0;
}
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
{
MDB_ID *ids = *idp, len = ids[0];
/* Too big? */
if (len + n > ids[-1]) {
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
return ENOMEM;
ids = *idp;
}
ids[0] = len + n;
ids += len;
while (n)
ids[n--] = id++;
return 0;
}
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
{
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
old_id = idl[j];
while (i) {
merge_id = merge[i--];
for (; old_id < merge_id; old_id = idl[--j])
idl[k--] = old_id;
idl[k--] = merge_id;
}
idl[0] = total;
}
/* Quicksort + Insertion sort for small arrays */
#define SMALL 8
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
void
mdb_midl_sort( MDB_IDL ids )
{
/* Max possible depth of int-indexed tree * 2 items/level */
int istack[sizeof(int)*CHAR_BIT * 2];
int i,j,k,l,ir,jstack;
MDB_ID a, itmp;
ir = (int)ids[0];
l = 1;
jstack = 0;
for(;;) {
if (ir - l < SMALL) { /* Insertion sort */
for (j=l+1;j<=ir;j++) {
a = ids[j];
for (i=j-1;i>=1;i--) {
if (ids[i] >= a) break;
ids[i+1] = ids[i];
}
ids[i+1] = a;
}
if (jstack == 0) break;
ir = istack[jstack--];
l = istack[jstack--];
} else {
k = (l + ir) >> 1; /* Choose median of left, center, right */
MIDL_SWAP(ids[k], ids[l+1]);
if (ids[l] < ids[ir]) {
MIDL_SWAP(ids[l], ids[ir]);
}
if (ids[l+1] < ids[ir]) {
MIDL_SWAP(ids[l+1], ids[ir]);
}
if (ids[l] < ids[l+1]) {
MIDL_SWAP(ids[l], ids[l+1]);
}
i = l+1;
j = ir;
a = ids[l+1];
for(;;) {
do i++; while(ids[i] > a);
do j--; while(ids[j] < a);
if (j < i) break;
MIDL_SWAP(ids[i],ids[j]);
}
ids[l+1] = ids[j];
ids[j] = a;
jstack += 2;
if (ir-i+1 >= j-l) {
istack[jstack] = ir;
istack[jstack-1] = i;
ir = j-1;
} else {
istack[jstack] = j-1;
istack[jstack-1] = l;
l = i;
}
}
}
}
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
{
/*
* binary search of id in ids
* if found, returns position of id
* if not found, returns first position greater than id
*/
unsigned base = 0;
unsigned cursor = 1;
int val = 0;
unsigned n = (unsigned)ids[0].mid;
while( 0 < n ) {
unsigned pivot = n >> 1;
cursor = base + pivot + 1;
val = CMP( id, ids[cursor].mid );
if( val < 0 ) {
n = pivot;
} else if ( val > 0 ) {
base = cursor;
n -= pivot + 1;
} else {
return cursor;
}
}
if( val > 0 ) {
++cursor;
}
return cursor;
}
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
{
unsigned x, i;
x = mdb_mid2l_search( ids, id->mid );
if( x < 1 ) {
/* internal error */
return -2;
}
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
/* duplicate */
return -1;
}
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
/* too big */
return -2;
} else {
/* insert id */
ids[0].mid++;
for (i=(unsigned)ids[0].mid; i>x; i--)
ids[i] = ids[i-1];
ids[x] = *id;
}
return 0;
}
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
{
/* Too big? */
if (ids[0].mid >= MDB_IDL_UM_MAX) {
return -2;
}
ids[0].mid++;
ids[ids[0].mid] = *id;
return 0;
}
/** @} */
/** @} */

View File

@@ -0,0 +1,186 @@
/** @file midl.h
* @brief LMDB ID List header file.
*
* This file was originally part of back-bdb but has been
* modified for use in libmdb. Most of the macros defined
* in this file are unused, just left over from the original.
*
* This file is only used internally in libmdb and its definitions
* are not exposed publicly.
*/
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2000-2014 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#ifndef _MDB_MIDL_H_
#define _MDB_MIDL_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @defgroup internal LMDB Internals
* @{
*/
/** @defgroup idls ID List Management
* @{
*/
/** A generic unsigned ID number. These were entryIDs in back-bdb.
* Preferably it should have the same size as a pointer.
*/
typedef size_t MDB_ID;
/** An IDL is an ID List, a sorted array of IDs. The first
* element of the array is a counter for how many actual
* IDs are in the list. In the original back-bdb code, IDLs are
* sorted in ascending order. For libmdb IDLs are sorted in
* descending order.
*/
typedef MDB_ID *MDB_IDL;
/* IDL sizes - likely should be even bigger
* limiting factors: sizeof(ID), thread stack size
*/
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
/** Current max length of an #mdb_midl_alloc()ed IDL */
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
/** Append ID to IDL. The IDL must be big enough. */
#define mdb_midl_xappend(idl, id) do { \
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
xidl[xlen] = (id); \
} while (0)
/** Search for an ID in an IDL.
* @param[in] ids The IDL to search.
* @param[in] id The ID to search for.
* @return The index of the first ID greater than or equal to \b id.
*/
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
/** Allocate an IDL.
* Allocates memory for an IDL of the given size.
* @return IDL on success, NULL on failure.
*/
MDB_IDL mdb_midl_alloc(int num);
/** Free an IDL.
* @param[in] ids The IDL to free.
*/
void mdb_midl_free(MDB_IDL ids);
/** Shrink an IDL.
* Return the IDL to the default size if it has grown larger.
* @param[in,out] idp Address of the IDL to shrink.
* @return 0 on no change, non-zero if shrunk.
*/
int mdb_midl_shrink(MDB_IDL *idp);
/** Make room for num additional elements in an IDL.
* @param[in,out] idp Address of the IDL.
* @param[in] num Number of elements to make room for.
* @return 0 on success, ENOMEM on failure.
*/
int mdb_midl_need(MDB_IDL *idp, unsigned num);
/** Append an ID onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] id The ID to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
/** Append an IDL onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] app The IDL to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
/** Append an ID range onto an IDL.
* @param[in,out] idp Address of the IDL to append to.
* @param[in] id The lowest ID to append.
* @param[in] n Number of IDs to append.
* @return 0 on success, ENOMEM if the IDL is too large.
*/
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
* @param[in] idl The IDL to merge into.
* @param[in] merge The IDL to merge.
*/
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
/** Sort an IDL.
* @param[in,out] ids The IDL to sort.
*/
void mdb_midl_sort( MDB_IDL ids );
/** An ID2 is an ID/pointer pair.
*/
typedef struct MDB_ID2 {
MDB_ID mid; /**< The ID */
void *mptr; /**< The pointer */
} MDB_ID2;
/** An ID2L is an ID2 List, a sorted array of ID2s.
* The first element's \b mid member is a count of how many actual
* elements are in the array. The \b mptr member of the first element is unused.
* The array is sorted in ascending order by \b mid.
*/
typedef MDB_ID2 *MDB_ID2L;
/** Search for an ID in an ID2L.
* @param[in] ids The ID2L to search.
* @param[in] id The ID to search for.
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
*/
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
/** Insert an ID2 into a ID2L.
* @param[in,out] ids The ID2L to insert into.
* @param[in] id The ID2 to insert.
* @return 0 on success, -1 if the ID was already present in the ID2L.
*/
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
/** Append an ID2 into a ID2L.
* @param[in,out] ids The ID2L to append into.
* @param[in] id The ID2 to append.
* @return 0 on success, -2 if the ID2L is too big.
*/
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* _MDB_MIDL_H_ */

View File

@@ -0,0 +1,173 @@
/* mtest.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor, *cur2;
MDB_cursor_op op;
int count;
int *values;
char sval[32] = "";
srand(time(NULL));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, NULL, 0, &dbi));
key.mv_size = sizeof(int);
key.mv_data = sval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
j++;
data.mv_size = sizeof(sval);
data.mv_data = sval;
}
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
key.mv_data = sval;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%03x ", values[i]);
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor last\n");
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor last/prev\n");
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
mdb_txn_abort(txn);
printf("Deleting with cursor\n");
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cur2));
for (i=0; i<50; i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
E(mdb_del(txn, dbi, &key, NULL));
}
printf("Restarting cursor in txn\n");
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cur2);
E(mdb_txn_commit(txn));
printf("Restarting cursor outside txn\n");
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
break;
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,123 @@
/* mtest2.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Just like mtest.c, but using a subDB instead of the main DB */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32] = "";
srand(time(NULL));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, "id1", MDB_CREATE, &dbi));
key.mv_size = sizeof(int);
key.mv_data = sval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
key.mv_data = sval;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%03x ", values[i]);
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,133 @@
/* mtest3.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32];
char kval[sizeof(int)];
srand(time(NULL));
memset(sval, 0, sizeof(sval));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
if (!(i & 0x0f))
sprintf(kval, "%03x", values[i]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,168 @@
/* mtest4.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs with fixed-size keys */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[8];
char kval[sizeof(int)];
memset(sval, 0, sizeof(sval));
count = 510;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = i*5;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
strcpy(kval, "001");
for (i=0;i<count;i++) {
sprintf(sval, "%07x", values[i]);
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
/* there should be one full page of dups now.
*/
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
/* test all 3 branches of split code:
* 1: new key in lower half
* 2: new key at split point
* 3: new key in upper half
*/
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
sprintf(sval, "%07x", values[3]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
mdb_txn_abort(txn);
sprintf(sval, "%07x", values[255]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
mdb_txn_abort(txn);
sprintf(sval, "%07x", values[500]+1);
E(mdb_txn_begin(env, NULL, 0, &txn));
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
E(mdb_txn_commit(txn));
/* Try MDB_NEXT_MULTIPLE */
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%3)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(sval, "%07x", values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,135 @@
/* mtest5.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for sorted duplicate DBs using cursor_put */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
char sval[32];
char kval[sizeof(int)];
srand(time(NULL));
memset(sval, 0, sizeof(sval));
count = (rand()%384) + 64;
values = (int *)malloc(count*sizeof(int));
for(i = 0;i<count;i++) {
values[i] = rand()%1024;
}
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
printf("Adding %d values\n", count);
for (i=0;i<count;i++) {
if (!(i & 0x0f))
sprintf(kval, "%03x", values[i]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
j++;
}
if (j) printf("%d duplicates skipped\n", j);
mdb_cursor_close(cursor);
E(mdb_txn_commit(txn));
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,137 @@
/* mtest6.c - memory-mapped database tester/toy */
/*
* Copyright 2011-2014 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* Tests for DB splits and merges */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "lmdb.h"
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
char dkbuf[1024];
int main(int argc,char * argv[])
{
int i = 0, j = 0, rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_stat mst;
MDB_cursor *cursor;
int count;
int *values;
long kval;
char *sval;
srand(time(NULL));
E(mdb_env_create(&env));
E(mdb_env_set_mapsize(env, 10485760));
E(mdb_env_set_maxdbs(env, 4));
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
E(mdb_stat(txn, dbi, &mst));
sval = calloc(1, mst.ms_psize / 4);
key.mv_size = sizeof(long);
key.mv_data = &kval;
data.mv_size = mst.ms_psize / 4 - 30;
data.mv_data = sval;
printf("Adding 12 values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5;
sprintf(sval, "%08x", kval);
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
printf("Adding 12 more values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5+4;
sprintf(sval, "%08x", kval);
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
printf("Adding 12 more values, should yield 3 splits\n");
for (i=0;i<12;i++) {
kval = i*5+1;
sprintf(sval, "%08x", kval);
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
}
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
do {
printf("key: %p %s, data: %p %.*s\n",
key.mv_data, mdb_dkey(&key, dkbuf),
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_txn_commit(txn);
#if 0
j=0;
for (i= count - 1; i > -1; i-= (rand()%5)) {
j++;
txn=NULL;
E(mdb_txn_begin(env, NULL, 0, &txn));
sprintf(kval, "%03x", values[i & ~0x0f]);
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
key.mv_size = sizeof(int);
key.mv_data = kval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
j--;
mdb_txn_abort(txn);
} else {
E(mdb_txn_commit(txn));
}
}
free(values);
printf("Deleted %d values\n", j);
E(mdb_env_stat(env, &mst));
E(mdb_txn_begin(env, NULL, 1, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
printf("Cursor next\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
printf("Cursor prev\n");
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
printf("key: %.*s, data: %.*s\n",
(int) key.mv_size, (char *) key.mv_data,
(int) data.mv_size, (char *) data.mv_data);
}
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
mdb_cursor_close(cursor);
mdb_close(env, dbi);
mdb_txn_abort(txn);
#endif
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,73 @@
/* sample-bdb.txt - BerkeleyDB toy/sample
*
* Do a line-by-line comparison of this and sample-mdb.txt
*/
/*
* Copyright 2012 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include <string.h>
#include <db.h>
int main(int argc,char * argv[])
{
int rc;
DB_ENV *env;
DB *dbi;
DBT key, data;
DB_TXN *txn;
DBC *cursor;
char sval[32], kval[32];
/* Note: Most error checking omitted for simplicity */
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
rc = db_env_create(&env, 0);
rc = env->open(env, "./testdb", FLAGS, 0664);
rc = db_create(&dbi, env, 0);
rc = env->txn_begin(env, NULL, &txn, 0);
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.size = sizeof(int);
key.data = sval;
data.size = sizeof(sval);
data.data = sval;
sprintf(sval, "%03x %d foo bar", 32, 3141592);
rc = dbi->put(dbi, txn, &key, &data, 0);
rc = txn->commit(txn, 0);
if (rc) {
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
goto leave;
}
rc = env->txn_begin(env, NULL, &txn, 0);
rc = dbi->cursor(dbi, txn, &cursor, 0);
key.flags = DB_DBT_USERMEM;
key.data = kval;
key.ulen = sizeof(kval);
data.flags = DB_DBT_USERMEM;
data.data = sval;
data.ulen = sizeof(sval);
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.data, (int) key.size, (char *) key.data,
data.data, (int) data.size, (char *) data.data);
}
rc = cursor->c_close(cursor);
rc = txn->abort(txn);
leave:
rc = dbi->close(dbi, 0);
rc = env->close(env, 0);
return rc;
}

View File

@@ -0,0 +1,62 @@
/* sample-mdb.txt - MDB toy/sample
*
* Do a line-by-line comparison of this and sample-bdb.txt
*/
/*
* Copyright 2012 Howard Chu, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include <stdio.h>
#include "lmdb.h"
int main(int argc,char * argv[])
{
int rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_cursor *cursor;
char sval[32];
/* Note: Most error checking omitted for simplicity */
rc = mdb_env_create(&env);
rc = mdb_env_open(env, "./testdb", 0, 0664);
rc = mdb_txn_begin(env, NULL, 0, &txn);
rc = mdb_open(txn, NULL, 0, &dbi);
key.mv_size = sizeof(int);
key.mv_data = sval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
sprintf(sval, "%03x %d foo bar", 32, 3141592);
rc = mdb_put(txn, dbi, &key, &data, 0);
rc = mdb_txn_commit(txn);
if (rc) {
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
goto leave;
}
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
rc = mdb_cursor_open(txn, dbi, &cursor);
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
printf("key: %p %.*s, data: %p %.*s\n",
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
leave:
mdb_close(env, dbi);
mdb_env_close(env);
return 0;
}

View File

@@ -0,0 +1,22 @@
<tagfile>
<compound kind="page">
<name>mdb_copy_1</name>
<title>mdb_copy - environment copy tool</title>
<filename>mdb_copy.1</filename>
</compound>
<compound kind="page">
<name>mdb_dump_1</name>
<title>mdb_dump - environment export tool</title>
<filename>mdb_dump.1</filename>
</compound>
<compound kind="page">
<name>mdb_load_1</name>
<title>mdb_load - environment import tool</title>
<filename>mdb_load.1</filename>
</compound>
<compound kind="page">
<name>mdb_stat_1</name>
<title>mdb_stat - environment status tool</title>
<filename>mdb_stat.1</filename>
</compound>
</tagfile>