diff options
author | George Hazan <george.hazan@gmail.com> | 2015-03-15 15:30:03 +0000 |
---|---|---|
committer | George Hazan <george.hazan@gmail.com> | 2015-03-15 15:30:03 +0000 |
commit | b81bd804e435e76592f9f281b717c696c3618fa2 (patch) | |
tree | 57c9295922457227265ba59e6f3f507ad038e194 /plugins | |
parent | 641c67a9d552b07664308c2ae3384cc95a75a2d0 (diff) |
initial release
git-svn-id: http://svn.miranda-ng.org/main/trunk@12409 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'plugins')
161 files changed, 46038 insertions, 0 deletions
diff --git a/plugins/Dbx_kv/Dbx_kv_10.vcxproj b/plugins/Dbx_kv/Dbx_kv_10.vcxproj new file mode 100644 index 0000000000..294d55fb4f --- /dev/null +++ b/plugins/Dbx_kv/Dbx_kv_10.vcxproj @@ -0,0 +1,322 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>Dbx_kv</ProjectName>
+ <ProjectGuid>{E0ACDEA0-0AC9-4431-8CA3-6B0CCACB2E18}</ProjectGuid>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30128.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)$(Configuration)64\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)$(Configuration)64\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
+ <IgnoreImportLibrary>true</IgnoreImportLibrary>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;_DEBUG;WIN32;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <StringPooling>false</StringPooling>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage\lib</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;_DEBUG;WIN32;WIN64;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>false</StringPooling>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level4</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>Full</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;NDEBUG;WIN32;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <ExceptionHandling>false</ExceptionHandling>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>Full</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;NDEBUG;WIN32;WIN64;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <ExceptionHandling>false</ExceptionHandling>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="src\dbcache.cpp" />
+ <ClCompile Include="src\dbcontacts.cpp" />
+ <ClCompile Include="src\dbcrypt.cpp" />
+ <ClCompile Include="src\dbevents.cpp" />
+ <ClCompile Include="src\dbintf.cpp" />
+ <ClCompile Include="src\dbmodulechain.cpp" />
+ <ClCompile Include="src\dbsettings.cpp" />
+ <ClCompile Include="src\hamsterdb\src\1base\error.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\util.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1errorinducer\errorinducer.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1globals\globals.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1mem\mem.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os_win32.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\2page\page.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_disk.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_inmem.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_check.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_erase.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_find.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_index.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_insert.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_stats.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_update.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_visit.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3changeset\changeset.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3journal\journal.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3page_manager\page_manager.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4cursor\cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\5hamsterdb\hamsterdb.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\stdafx.cpp">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\init.cpp" />
+ <ClCompile Include="src\ui.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\m_database.h" />
+ <ClInclude Include="..\..\include\m_db_int.h" />
+ <ClInclude Include="src\commonheaders.h" />
+ <ClInclude Include="src\dbintf.h" />
+ <ClInclude Include="src\hamsterdb\config.h" />
+ <ClInclude Include="src\resource.h" />
+ <ClInclude Include="src\version.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\dbx_kv.rc" />
+ <ResourceCompile Include="res\version.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/plugins/Dbx_kv/Dbx_kv_10.vcxproj.filters b/plugins/Dbx_kv/Dbx_kv_10.vcxproj.filters new file mode 100644 index 0000000000..6ac240c4f6 --- /dev/null +++ b/plugins/Dbx_kv/Dbx_kv_10.vcxproj.filters @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{60d2ae39-2690-4258-aa90-e825ef024b81}</UniqueIdentifier>
+ <Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{a2637eb4-34a4-4341-9c84-0e0ecfdc0f35}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{e5e10389-c0d7-4cf8-8442-6714f0d5aff1}</UniqueIdentifier>
+ <Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
+ </Filter>
+ <Filter Include="Source Files\hamsterdb">
+ <UniqueIdentifier>{74e7bd75-7fa5-4c0b-b9a1-62066a38f420}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="src\dbcontacts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbevents.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbintf.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbmodulechain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbsettings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\init.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\stdafx.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbcrypt.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\ui.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\error.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\util.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1errorinducer\errorinducer.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1globals\globals.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1mem\mem.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os_win32.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\2page\page.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_disk.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_inmem.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_check.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_erase.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_find.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_index.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_insert.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_stats.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_update.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_visit.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3changeset\changeset.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3journal\journal.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3page_manager\page_manager.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4cursor\cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\5hamsterdb\hamsterdb.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="src\commonheaders.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\dbintf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\m_database.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\m_db_int.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\hamsterdb\config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\dbx_kv.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="res\version.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project>
\ No newline at end of file diff --git a/plugins/Dbx_kv/Dbx_kv_12.vcxproj b/plugins/Dbx_kv/Dbx_kv_12.vcxproj new file mode 100644 index 0000000000..613ee01e9a --- /dev/null +++ b/plugins/Dbx_kv/Dbx_kv_12.vcxproj @@ -0,0 +1,327 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>Dbx_kv</ProjectName>
+ <ProjectGuid>{A9F9E806-8BA6-4C6B-B511-B979D365FF54}</ProjectGuid>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>v120_xp</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>v120_xp</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <PlatformToolset>v120_xp</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>v120_xp</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30128.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)$(Configuration)64\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)$(Configuration)64\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
+ <IgnoreImportLibrary>true</IgnoreImportLibrary>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;_DEBUG;WIN32;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level4</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <StringPooling>false</StringPooling>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage\lib</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <ProjectReference />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;_DEBUG;WIN32;WIN64;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>false</StringPooling>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level4</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <ProjectReference />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>Full</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;NDEBUG;WIN32;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level4</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <ProjectReference />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>Full</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost;src\hamsterdb\include;src\hamsterdb\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>HAVE_CONFIG_H;NDEBUG;WIN32;WIN64;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <FloatingPointModel>Fast</FloatingPointModel>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>commonheaders.h</PrecompiledHeaderFile>
+ <WarningLevel>Level4</WarningLevel>
+ <DisableSpecificWarnings>4706</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\include;..\..\include\msapi</AdditionalIncludeDirectories>
+ </ResourceCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>$(ProfileDir)..\..\bin12\lib;..\..\..\boost\stage64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ <ProjectReference />
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="src\dbcontacts.cpp" />
+ <ClCompile Include="src\dbcrypt.cpp" />
+ <ClCompile Include="src\dbevents.cpp" />
+ <ClCompile Include="src\dbintf.cpp" />
+ <ClCompile Include="src\dbmodulechain.cpp" />
+ <ClCompile Include="src\dbsettings.cpp" />
+ <ClCompile Include="src\hamsterdb\src\1base\error.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\util.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1errorinducer\errorinducer.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1globals\globals.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1mem\mem.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os_win32.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\2page\page.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_disk.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_inmem.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_check.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_erase.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_find.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_index.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_insert.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_stats.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_update.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_visit.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3changeset\changeset.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3journal\journal.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3page_manager\page_manager.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4cursor\cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_cursor.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_local.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_remote.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\5hamsterdb\hamsterdb.cc">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\stdafx.cpp">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\init.cpp" />
+ <ClCompile Include="src\ui.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\m_database.h" />
+ <ClInclude Include="..\..\include\m_db_int.h" />
+ <ClInclude Include="src\commonheaders.h" />
+ <ClInclude Include="src\dbintf.h" />
+ <ClInclude Include="src\hamsterdb\config.h" />
+ <ClInclude Include="src\resource.h" />
+ <ClInclude Include="src\version.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\dbx_kv.rc" />
+ <ResourceCompile Include="res\version.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/plugins/Dbx_kv/Dbx_kv_12.vcxproj.filters b/plugins/Dbx_kv/Dbx_kv_12.vcxproj.filters new file mode 100644 index 0000000000..6ac240c4f6 --- /dev/null +++ b/plugins/Dbx_kv/Dbx_kv_12.vcxproj.filters @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{60d2ae39-2690-4258-aa90-e825ef024b81}</UniqueIdentifier>
+ <Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{a2637eb4-34a4-4341-9c84-0e0ecfdc0f35}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{e5e10389-c0d7-4cf8-8442-6714f0d5aff1}</UniqueIdentifier>
+ <Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
+ </Filter>
+ <Filter Include="Source Files\hamsterdb">
+ <UniqueIdentifier>{74e7bd75-7fa5-4c0b-b9a1-62066a38f420}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="src\dbcontacts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbevents.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbintf.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbmodulechain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbsettings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\init.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\stdafx.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\dbcrypt.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\ui.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\error.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1base\util.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1errorinducer\errorinducer.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1globals\globals.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1mem\mem.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\1os\os_win32.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\2page\page.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_disk.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3blob_manager\blob_manager_inmem.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_check.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_erase.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_find.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_index.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_insert.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_stats.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_update.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3btree\btree_visit.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3changeset\changeset.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3journal\journal.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\3page_manager\page_manager.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4cursor\cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4db\db_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4env\env.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_cursor.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_local.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\4txn\txn_remote.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ <ClCompile Include="src\hamsterdb\src\5hamsterdb\hamsterdb.cc">
+ <Filter>Source Files\hamsterdb</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="src\commonheaders.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\dbintf.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\m_database.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\m_db_int.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\hamsterdb\config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\dbx_kv.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="res\version.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project>
\ No newline at end of file diff --git a/plugins/Dbx_kv/res/dbx_kv.rc b/plugins/Dbx_kv/res/dbx_kv.rc new file mode 100644 index 0000000000..8fc4ecbdc5 --- /dev/null +++ b/plugins/Dbx_kv/res/dbx_kv.rc @@ -0,0 +1,160 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "..\src\resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include <winres.h>
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_LOGIN DIALOGEX 0, 0, 190, 86
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW
+CAPTION "Login to Miranda NG"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_HEADERBAR,"MHeaderbarCtrl",0x0,0,0,190,26
+ CTEXT "",IDC_LANG,158,34,13,13,SS_CENTERIMAGE | NOT WS_GROUP
+ EDITTEXT IDC_USERPASS,21,34,128,14,ES_PASSWORD | ES_AUTOHSCROLL | WS_GROUP
+ DEFPUSHBUTTON "OK",IDOK,36,64,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,102,64,50,14
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,55,190,1
+END
+
+IDD_NEWPASS DIALOGEX 0, 0, 190, 102
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW
+CAPTION "New password"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Please enter your new password",IDC_HEADERBAR,
+ "MHeaderbarCtrl",0x0,0,0,190,26
+ CTEXT "",IDC_LANG,158,34,13,13,SS_CENTERIMAGE | NOT WS_GROUP
+ EDITTEXT IDC_USERPASS1,21,34,128,14,ES_PASSWORD | ES_AUTOHSCROLL
+ EDITTEXT IDC_USERPASS2,21,54,128,14,ES_PASSWORD | ES_AUTOHSCROLL
+ DEFPUSHBUTTON "OK",IDOK,36,84,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,100,84,50,14
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,77,190,1
+END
+
+IDD_CHANGEPASS DIALOGEX 0, 0, 190, 148
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_TOOLWINDOW
+CAPTION "Enter password"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Change password",IDC_HEADERBAR,"MHeaderbarCtrl",0x0,0,0,190,26
+ CTEXT "",IDC_LANG,158,42,13,13,SS_CENTERIMAGE | NOT WS_GROUP
+ EDITTEXT IDC_OLDPASS,21,42,128,14,ES_PASSWORD | ES_AUTOHSCROLL
+ EDITTEXT IDC_USERPASS1,21,77,128,14,ES_PASSWORD | ES_AUTOHSCROLL
+ EDITTEXT IDC_USERPASS2,21,98,128,14,ES_PASSWORD | ES_AUTOHSCROLL
+ DEFPUSHBUTTON "Change",IDOK,11,127,50,14
+ PUSHBUTTON "Remove",IDREMOVE,69,127,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,126,127,50,14
+ LTEXT "New password",IDC_STATIC,11,66,163,10,0,WS_EX_TRANSPARENT
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,119,190,1
+ LTEXT "Old password",IDC_STATIC,11,31,140,10,0,WS_EX_TRANSPARENT
+END
+
+IDD_OPTIONS DIALOGEX 0, 0, 318, 176
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Database encryption mode",IDC_STATIC,6,22,305,125
+ CONTROL "Standard",IDC_STANDARD,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,12,38,292,12
+ CONTROL "Total",IDC_TOTAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,12,95,292,12
+ LTEXT "Only critical data are encrypted (passwords, security tokens, etc). All another settings and history remains unencrypted. Fast and effective, suitable for the most cases",IDC_STATIC,22,54,284,37
+ LTEXT "All string settings and all events in histories are encrypted. It also makes Miranda much slower and creates a risk of losing everything you've stored in a profile in case of losing password. Recommended only for paranoid users",IDC_STATIC,22,110,284,33
+ PUSHBUTTON "Set password",IDC_USERPASS,200,153,111,17
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_LOGO ICON "logo.ico"
+IDI_ICONPASS ICON "pass.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_LOGIN, DIALOG
+ BEGIN
+ END
+
+ IDD_CHANGEPASS, DIALOG
+ BEGIN
+ END
+
+ IDD_OPTIONS, DIALOG
+ BEGIN
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "..\\src\\resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include <winres.h>\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/plugins/Dbx_kv/res/logo.ico b/plugins/Dbx_kv/res/logo.ico Binary files differnew file mode 100644 index 0000000000..f49bbe83d6 --- /dev/null +++ b/plugins/Dbx_kv/res/logo.ico diff --git a/plugins/Dbx_kv/res/pass.ico b/plugins/Dbx_kv/res/pass.ico Binary files differnew file mode 100644 index 0000000000..dc47a6ed4f --- /dev/null +++ b/plugins/Dbx_kv/res/pass.ico diff --git a/plugins/Dbx_kv/res/version.rc b/plugins/Dbx_kv/res/version.rc new file mode 100644 index 0000000000..5bfbab4754 --- /dev/null +++ b/plugins/Dbx_kv/res/version.rc @@ -0,0 +1,38 @@ +// Microsoft Visual C++ generated resource script.
+//
+#ifdef APSTUDIO_INVOKED
+#error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+#include "afxres.h"
+#include "..\src\version.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION __FILEVERSION_STRING
+ PRODUCTVERSION __FILEVERSION_STRING
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x0L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004b0"
+ BEGIN
+ VALUE "FileDescription", __DESCRIPTION
+ VALUE "InternalName", __PLUGIN_NAME
+ VALUE "LegalCopyright", __COPYRIGHT
+ VALUE "OriginalFilename", __FILENAME
+ VALUE "ProductName", __PLUGIN_NAME
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0, 1200
+ END
+END
diff --git a/plugins/Dbx_kv/src/commonheaders.h b/plugins/Dbx_kv/src/commonheaders.h new file mode 100644 index 0000000000..6dbdcd6c11 --- /dev/null +++ b/plugins/Dbx_kv/src/commonheaders.h @@ -0,0 +1,80 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#define _CRT_SECURE_NO_WARNINGS
+#define _WIN32_WINNT 0x0501
+
+#pragma warning(disable:4509)
+
+#include <windows.h>
+#include <time.h>
+#include <process.h>
+#include <memory>
+
+#include <newpluginapi.h>
+#include <win2k.h>
+#include <m_system_cpp.h>
+#include <m_database.h>
+#include <m_langpack.h>
+#include <m_clist.h>
+#include <m_icolib.h>
+#include <m_options.h>
+#include <m_crypto.h>
+#include <m_metacontacts.h>
+#include <m_protocols.h>
+#include <m_netlib.h>
+
+#include "ham/hamsterdb.h"
+
+#include "dbintf.h"
+#include "resource.h"
+#include "version.h"
+
+class cursor_ptr
+{
+ ham_cursor_t *m_cursor;
+
+public:
+ __forceinline cursor_ptr(ham_db_t *_dbi)
+ {
+ if (ham_cursor_create(&m_cursor, _dbi, NULL, 0) != HAM_SUCCESS)
+ m_cursor = NULL;
+ }
+
+ __forceinline ~cursor_ptr()
+ {
+ if (m_cursor)
+ ham_cursor_close(m_cursor);
+ }
+
+ __forceinline operator ham_cursor_t*() const { return m_cursor; }
+};
+
+extern HINSTANCE g_hInst;
+extern LIST<CDbxKV> g_Dbs;
+
+#ifdef __GNUC__
+#define mir_i64(x) (x##LL)
+#else
+#define mir_i64(x) (x##i64)
+#endif
diff --git a/plugins/Dbx_kv/src/dbcache.cpp b/plugins/Dbx_kv/src/dbcache.cpp new file mode 100644 index 0000000000..e7e7c263b8 --- /dev/null +++ b/plugins/Dbx_kv/src/dbcache.cpp @@ -0,0 +1,25 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
diff --git a/plugins/Dbx_kv/src/dbcontacts.cpp b/plugins/Dbx_kv/src/dbcontacts.cpp new file mode 100644 index 0000000000..41c24e9f70 --- /dev/null +++ b/plugins/Dbx_kv/src/dbcontacts.cpp @@ -0,0 +1,262 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+int CDbxKV::CheckProto(DBCachedContact *cc, const char *proto)
+{
+ if (cc->szProto == NULL) {
+ char protobuf[MAX_PATH] = { 0 };
+ DBVARIANT dbv;
+ dbv.type = DBVT_ASCIIZ;
+ dbv.pszVal = protobuf;
+ dbv.cchVal = sizeof(protobuf);
+ if (GetContactSettingStatic(cc->contactID, "Protocol", "p", &dbv) != 0 || (dbv.type != DBVT_ASCIIZ))
+ return 0;
+
+ cc->szProto = m_cache->GetCachedSetting(NULL, protobuf, 0, (int)strlen(protobuf));
+ }
+
+ return !strcmp(cc->szProto, proto);
+}
+
+STDMETHODIMP_(LONG) CDbxKV::GetContactCount(void)
+{
+ mir_cslock lck(m_csDbAccess);
+ return m_contactCount;
+}
+
+STDMETHODIMP_(LONG) CDbxKV::GetContactSize(void)
+{
+ return sizeof(DBCachedContact);
+}
+
+STDMETHODIMP_(MCONTACT) CDbxKV::FindFirstContact(const char *szProto)
+{
+ mir_cslock lck(m_csDbAccess);
+ DBCachedContact *cc = m_cache->GetFirstContact();
+ if (cc == NULL)
+ return NULL;
+
+ if (cc->contactID == 0)
+ if ((cc = m_cache->GetNextContact(0)) == NULL)
+ return NULL;
+
+ if (!szProto || CheckProto(cc, szProto))
+ return cc->contactID;
+
+ return FindNextContact(cc->contactID, szProto);
+}
+
+STDMETHODIMP_(MCONTACT) CDbxKV::FindNextContact(MCONTACT contactID, const char *szProto)
+{
+ mir_cslock lck(m_csDbAccess);
+ while (contactID) {
+ DBCachedContact *cc = m_cache->GetNextContact(contactID);
+ if (cc == NULL)
+ break;
+
+ if (!szProto || CheckProto(cc, szProto))
+ return cc->contactID;
+
+ contactID = cc->contactID;
+ }
+
+ return NULL;
+}
+
+STDMETHODIMP_(LONG) CDbxKV::DeleteContact(MCONTACT contactID)
+{
+ if (contactID == 0) // global contact cannot be removed
+ return 1;
+
+ // call notifier while outside mutex
+ NotifyEventHooks(hContactDeletedEvent, contactID, 0);
+
+ // delete
+ mir_cslock lck(m_csDbAccess);
+
+ ham_key_t key = { sizeof(MCONTACT), &contactID };
+ ham_db_erase(m_dbContacts, NULL, &key, 0);
+ return 0;
+}
+
+STDMETHODIMP_(MCONTACT) CDbxKV::AddContact()
+{
+ DWORD dwContactId;
+ {
+ mir_cslock lck(m_csDbAccess);
+ dwContactId = m_dwMaxContactId++;
+
+ DBCachedContact *cc = m_cache->AddContactToCache(dwContactId);
+ cc->dbc.dwSignature = DBCONTACT_SIGNATURE;
+
+ ham_key_t key = { sizeof(MCONTACT), &dwContactId };
+ ham_record_t data = { sizeof(cc->dbc), &cc->dbc };
+ ham_db_insert(m_dbContacts, NULL, &key, &data, HAM_OVERWRITE);
+ }
+
+ NotifyEventHooks(hContactAddedEvent, dwContactId, 0);
+ return dwContactId;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::IsDbContact(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc != NULL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// metacontacts support
+
+BOOL CDbxKV::MetaDetouchSub(DBCachedContact *cc, int nSub)
+{
+ CallService(MS_DB_MODULE_DELETE, cc->pSubs[nSub], (LPARAM)META_PROTO);
+ return 0;
+}
+
+BOOL CDbxKV::MetaSetDefault(DBCachedContact *cc)
+{
+ return db_set_dw(cc->contactID, META_PROTO, "Default", cc->nDefault);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL CDbxKV::MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub)
+{
+ DBEventSortingKey keyVal = { ccSub->contactID, 0, 0 }, insVal = { ccMeta->contactID, 0, 0 };
+ ham_key_t key = { sizeof(keyVal), &keyVal }, key2 = { sizeof(insVal), &insVal };
+ ham_record_t data;
+
+ cursor_ptr cursor(m_dbEventsSort);
+ if (ham_cursor_find(cursor, &key, &data, HAM_FIND_GT_MATCH) != HAM_SUCCESS)
+ return 0;
+
+ do {
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ if (pKey->dwContactId != ccSub->contactID)
+ break;
+
+ insVal.ts = pKey->ts;
+ insVal.dwEventId = pKey->dwEventId;
+ ham_db_insert(m_dbEventsSort, NULL, &key2, &data, HAM_OVERWRITE);
+
+ ccMeta->dbc.dwEventCount++;
+ } while (ham_cursor_move(cursor, &key, &data, HAM_CURSOR_NEXT) == HAM_SUCCESS);
+
+ // now update the number of events in a metacontact
+ ham_key_t keyc = { sizeof(int), &ccMeta->contactID };
+ ham_record_t datac = { sizeof(ccMeta->dbc), &ccMeta->dbc };
+ ham_db_insert(m_dbContacts, NULL, &keyc, &datac, HAM_OVERWRITE);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL CDbxKV::MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub)
+{
+ DBEventSortingKey keyVal = { ccSub->contactID, 0, 0 }, delVal = { ccMeta->contactID, 0, 0 };
+ ham_key_t key = { sizeof(keyVal), &keyVal }, key2 = { sizeof(delVal), &delVal };
+ ham_record_t data;
+
+ cursor_ptr cursor(m_dbEventsSort);
+ if (ham_cursor_find(cursor, &key, &data, HAM_FIND_GT_MATCH) != HAM_SUCCESS)
+ return 0;
+
+ do {
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ if (pKey->dwContactId != ccSub->contactID)
+ break;
+
+ delVal.ts = pKey->ts;
+ delVal.dwEventId = pKey->dwEventId;
+ ham_db_erase(m_dbEventsSort, NULL, &key2, 0);
+
+ ccMeta->dbc.dwEventCount--;
+ } while (ham_cursor_move(cursor, &key, &data, HAM_CURSOR_NEXT) == HAM_SUCCESS);
+
+ // now update the number of events in a metacontact
+ ham_key_t keyc = { sizeof(int), &ccMeta->contactID };
+ ham_record_t datac = { sizeof(ccMeta->dbc), &ccMeta->dbc };
+ ham_db_insert(m_dbContacts, NULL, &keyc, &datac, HAM_OVERWRITE);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void DBCachedContact::Advance(DWORD id, DBEvent &dbe)
+{
+ dbc.dwEventCount++;
+
+ if (dbe.flags & (DBEF_READ | DBEF_SENT))
+ return;
+
+ if (dbe.timestamp < dbc.tsFirstUnread || dbc.tsFirstUnread == 0) {
+ dbc.tsFirstUnread = dbe.timestamp;
+ dbc.dwFirstUnread = id;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// initial cycle to fill the contacts' cache
+
+void CDbxKV::FillContacts()
+{
+ m_contactCount = 0;
+
+ ham_key_t key = { 0 };
+ ham_record_t rec = { 0 };
+ cursor_ptr cursor(m_dbContacts);
+ if (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_FIRST) != HAM_SUCCESS) // empty table?
+ return;
+
+ do {
+ DBContact *dbc = (DBContact*)rec.data;
+ if (dbc->dwSignature != DBCONTACT_SIGNATURE)
+ DatabaseCorruption(NULL);
+
+ DBCachedContact *cc = m_cache->AddContactToCache(*(DWORD*)key.data);
+ cc->dbc.dwSignature = DBCONTACT_SIGNATURE;
+ cc->dbc.dwEventCount = dbc->dwEventCount;
+ cc->dbc.dwFirstUnread = dbc->dwFirstUnread;
+ cc->dbc.tsFirstUnread = dbc->tsFirstUnread;
+
+ CheckProto(cc, "");
+
+ m_dwMaxContactId = cc->contactID+1;
+ m_contactCount++;
+
+ DBVARIANT dbv; dbv.type = DBVT_DWORD;
+ cc->nSubs = (0 != GetContactSetting(cc->contactID, META_PROTO, "NumContacts", &dbv)) ? -1 : dbv.dVal;
+ if (cc->nSubs != -1) {
+ cc->pSubs = (MCONTACT*)mir_alloc(cc->nSubs*sizeof(MCONTACT));
+ for (int i = 0; i < cc->nSubs; i++) {
+ char setting[100];
+ mir_snprintf(setting, SIZEOF(setting), "Handle%d", i);
+ cc->pSubs[i] = (0 != GetContactSetting(cc->contactID, META_PROTO, setting, &dbv)) ? NULL : dbv.dVal;
+ }
+ }
+ cc->nDefault = (0 != GetContactSetting(cc->contactID, META_PROTO, "Default", &dbv)) ? -1 : dbv.dVal;
+ cc->parentID = (0 != GetContactSetting(cc->contactID, META_PROTO, "ParentMeta", &dbv)) ? NULL : dbv.dVal;
+ } while (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_NEXT) == HAM_SUCCESS);
+}
diff --git a/plugins/Dbx_kv/src/dbcrypt.cpp b/plugins/Dbx_kv/src/dbcrypt.cpp new file mode 100644 index 0000000000..b79639fa61 --- /dev/null +++ b/plugins/Dbx_kv/src/dbcrypt.cpp @@ -0,0 +1,263 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+//VERY VERY VERY BASIC ENCRYPTION FUNCTION
+
+static void Encrypt(char *msg, BOOL up)
+{
+ int jump = (up) ? 5 : -5;
+ for (int i = 0; msg[i]; i++)
+ msg[i] = msg[i] + jump;
+}
+
+__forceinline void DecodeString(LPSTR buf)
+{
+ Encrypt(buf, FALSE);
+}
+
+struct VarDescr
+{
+ VarDescr(LPCSTR var, LPCSTR value) :
+ szVar(mir_strdup(var)),
+ szValue(mir_strdup(value))
+ {}
+
+ VarDescr(LPCSTR var, LPSTR value) :
+ szVar(mir_strdup(var)),
+ szValue(value)
+ {}
+
+ VarDescr(LPCSTR var, PBYTE value, int len) :
+ szVar(mir_strdup(var)),
+ szValue((char*)memcpy(mir_alloc(len), value, len)),
+ iLen(len)
+ {}
+
+ ptrA szVar, szValue;
+ int iLen;
+};
+
+struct SettingUgraderParam
+{
+ CDbxKV *db;
+ LPCSTR szModule;
+ MCONTACT contactID;
+ OBJLIST<VarDescr>* pList;
+};
+
+int sttSettingUgrader(const char *szSetting, LPARAM lParam)
+{
+ SettingUgraderParam *param = (SettingUgraderParam*)lParam;
+ if (param->db->IsSettingEncrypted(param->szModule, szSetting)) {
+ DBVARIANT dbv = { DBVT_UTF8 };
+ if (!param->db->GetContactSettingStr(param->contactID, param->szModule, szSetting, &dbv)) {
+ if (dbv.type == DBVT_UTF8) {
+ DecodeString(dbv.pszVal);
+ param->pList->insert(new VarDescr(szSetting, (LPCSTR)dbv.pszVal));
+ }
+ param->db->FreeVariant(&dbv);
+ }
+ }
+ return 0;
+}
+
+void sttContactEnum(MCONTACT contactID, const char *szModule, CDbxKV *db)
+{
+ OBJLIST<VarDescr> arSettings(1);
+ SettingUgraderParam param = { db, szModule, contactID, &arSettings };
+
+ DBCONTACTENUMSETTINGS dbces = { 0 };
+ dbces.pfnEnumProc = sttSettingUgrader;
+ dbces.szModule = szModule;
+ dbces.lParam = (LPARAM)¶m;
+ db->EnumContactSettings(NULL, &dbces);
+
+ for (int i = 0; i < arSettings.getCount(); i++) {
+ VarDescr &p = arSettings[i];
+
+ size_t len;
+ BYTE *pResult = db->m_crypto->encodeString(p.szValue, &len);
+ if (pResult != NULL) {
+ DBCONTACTWRITESETTING dbcws = { szModule, p.szVar };
+ dbcws.value.type = DBVT_ENCRYPTED;
+ dbcws.value.pbVal = pResult;
+ dbcws.value.cpbVal = (WORD)len;
+ db->WriteContactSetting(contactID, &dbcws);
+
+ mir_free(pResult);
+ }
+ }
+}
+
+int sttModuleEnum(const char *szModule, DWORD, LPARAM lParam)
+{
+ CDbxKV *db = (CDbxKV*)lParam;
+ sttContactEnum(NULL, szModule, db);
+
+ for (MCONTACT contactID = db->FindFirstContact(); contactID; contactID = db->FindNextContact(contactID))
+ sttContactEnum(contactID, szModule, db);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CDbxKV::InitCrypt()
+{
+ CRYPTO_PROVIDER *pProvider;
+ bool bMissingKey = false;
+
+ DBVARIANT dbv = { 0 };
+ dbv.type = DBVT_BLOB;
+ if (GetContactSetting(NULL, "CryptoEngine", "Provider", &dbv)) {
+ LBL_CreateProvider:
+ CRYPTO_PROVIDER **ppProvs;
+ int iNumProvs;
+ Crypto_EnumProviders(&iNumProvs, &ppProvs);
+ if (iNumProvs == 0)
+ return 1;
+
+ pProvider = ppProvs[0]; //!!!!!!!!!!!!!!!!!!
+
+ DBCONTACTWRITESETTING dbcws = { "CryptoEngine", "Provider" };
+ dbcws.value.type = DBVT_BLOB;
+ dbcws.value.pbVal = (PBYTE)pProvider->pszName;
+ dbcws.value.cpbVal = (int)strlen(pProvider->pszName) + 1;
+ WriteContactSetting(NULL, &dbcws);
+ }
+ else {
+ if (dbv.type != DBVT_BLOB) { // old version, clean it up
+ bMissingKey = true;
+ goto LBL_CreateProvider;
+ }
+
+ pProvider = Crypto_GetProvider(LPCSTR(dbv.pbVal));
+ FreeVariant(&dbv);
+ if (pProvider == NULL)
+ goto LBL_CreateProvider;
+ }
+
+ if ((m_crypto = pProvider->pFactory()) == NULL)
+ return 3;
+
+ dbv.type = DBVT_BLOB;
+ if (GetContactSetting(NULL, "CryptoEngine", "StoredKey", &dbv)) {
+ bMissingKey = true;
+
+ LBL_SetNewKey:
+ m_crypto->generateKey(); // unencrypted key
+ StoreKey();
+ }
+ else {
+ size_t iKeyLength = m_crypto->getKeyLength();
+ if (dbv.cpbVal != (WORD)iKeyLength)
+ goto LBL_SetNewKey;
+
+ if (!m_crypto->setKey(dbv.pbVal, iKeyLength))
+ if (!EnterPassword(dbv.pbVal, iKeyLength)) // password protected?
+ return 4;
+
+ FreeVariant(&dbv);
+ }
+
+ if (bMissingKey)
+ EnumModuleNames(sttModuleEnum, this);
+
+ dbv.type = DBVT_BYTE;
+ if (!GetContactSetting(NULL, "CryptoEngine", "DatabaseEncryption", &dbv))
+ m_bEncrypted = dbv.bVal != 0;
+
+ InitDialogs();
+ return 0;
+}
+
+void CDbxKV::StoreKey()
+{
+ size_t iKeyLength = m_crypto->getKeyLength();
+ BYTE *pKey = (BYTE*)_alloca(iKeyLength);
+ m_crypto->getKey(pKey, iKeyLength);
+
+ DBCONTACTWRITESETTING dbcws = { "CryptoEngine", "StoredKey" };
+ dbcws.value.type = DBVT_BLOB;
+ dbcws.value.cpbVal = (WORD)iKeyLength;
+ dbcws.value.pbVal = pKey;
+ WriteContactSetting(NULL, &dbcws);
+
+ SecureZeroMemory(pKey, iKeyLength);
+}
+
+void CDbxKV::SetPassword(LPCTSTR ptszPassword)
+{
+ if (ptszPassword == NULL || *ptszPassword == 0) {
+ m_bUsesPassword = false;
+ m_crypto->setPassword(NULL);
+ }
+ else {
+ m_bUsesPassword = true;
+ m_crypto->setPassword(ptrA(mir_utf8encodeT(ptszPassword)));
+ }
+ UpdateMenuItem();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxKV::ToggleEncryption()
+{
+ HANDLE hSave1 = hSettingChangeEvent; hSettingChangeEvent = NULL;
+ HANDLE hSave2 = hEventAddedEvent; hEventAddedEvent = NULL;
+ HANDLE hSave3 = hEventDeletedEvent; hEventDeletedEvent = NULL;
+ HANDLE hSave4 = hEventFilterAddedEvent; hEventFilterAddedEvent = NULL;
+
+ mir_cslock lck(m_csDbAccess);
+ ToggleSettingsEncryption(NULL);
+ ToggleEventsEncryption(NULL);
+
+ for (MCONTACT contactID = FindFirstContact(); contactID; contactID = FindNextContact(contactID)) {
+ ToggleSettingsEncryption(contactID);
+ ToggleEventsEncryption(contactID);
+ }
+
+ m_bEncrypted = !m_bEncrypted;
+
+ DBCONTACTWRITESETTING dbcws = { "CryptoEngine", "DatabaseEncryption" };
+ dbcws.value.type = DBVT_BYTE;
+ dbcws.value.bVal = m_bEncrypted;
+ WriteContactSetting(NULL, &dbcws);
+
+ hSettingChangeEvent = hSave1;
+ hEventAddedEvent = hSave2;
+ hEventDeletedEvent = hSave3;
+ hEventFilterAddedEvent = hSave4;
+}
+
+void CDbxKV::ToggleSettingsEncryption(MCONTACT contactID)
+{
+}
+
+void CDbxKV::ToggleEventsEncryption(MCONTACT contactID)
+{
+}
diff --git a/plugins/Dbx_kv/src/dbevents.cpp b/plugins/Dbx_kv/src/dbevents.cpp new file mode 100644 index 0000000000..466a732189 --- /dev/null +++ b/plugins/Dbx_kv/src/dbevents.cpp @@ -0,0 +1,358 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+STDMETHODIMP_(LONG) CDbxKV::GetEventCount(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc == NULL) ? 0 : cc->dbc.dwEventCount;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::AddEvent(MCONTACT contactID, DBEVENTINFO *dbei)
+{
+ if (dbei == NULL || dbei->cbSize != sizeof(DBEVENTINFO)) return 0;
+ if (dbei->timestamp == 0) return 0;
+
+ DBEvent dbe;
+ dbe.dwSignature = DBEVENT_SIGNATURE;
+ dbe.contactID = contactID; // store native or subcontact's id
+ dbe.ofsModuleName = GetModuleNameOfs(dbei->szModule);
+ dbe.timestamp = dbei->timestamp;
+ dbe.flags = dbei->flags;
+ dbe.wEventType = dbei->eventType;
+ dbe.cbBlob = dbei->cbBlob;
+ BYTE *pBlob = dbei->pBlob;
+
+ MCONTACT contactNotifyID = contactID;
+ DBCachedContact *cc, *ccSub = NULL;
+ if ((cc = m_cache->GetCachedContact(contactID)) == NULL)
+ return 0;
+
+ if (cc->IsSub()) {
+ ccSub = cc;
+ // set default sub to the event's source
+ if (!(dbei->flags & DBEF_SENT))
+ db_mc_setDefault(cc->parentID, contactID, false);
+ contactID = cc->parentID; // and add an event to a metahistory
+ if (db_mc_isEnabled())
+ contactNotifyID = contactID;
+ }
+
+ if (m_safetyMode)
+ if (NotifyEventHooks(hEventFilterAddedEvent, contactNotifyID, (LPARAM)dbei))
+ return NULL;
+
+ mir_ptr<BYTE> pCryptBlob;
+ if (m_bEncrypted) {
+ size_t len;
+ BYTE *pResult = m_crypto->encodeBuffer(pBlob, dbe.cbBlob, &len);
+ if (pResult != NULL) {
+ pCryptBlob = pBlob = pResult;
+ dbe.cbBlob = (DWORD)len;
+ dbe.flags |= DBEF_ENCRYPTED;
+ }
+ }
+
+ DWORD dwEventId = ++m_dwMaxEventId;
+
+ BYTE *pDest = (BYTE*)_alloca(sizeof(DBEvent) + dbe.cbBlob);
+ memcpy(pDest, &dbe, sizeof(DBEvent));
+ memcpy(pDest + sizeof(DBEvent), pBlob, dbe.cbBlob);
+
+ ham_key_t key = { sizeof(int), &dwEventId };
+ ham_record_t rec = { sizeof(DBEvent) + dbe.cbBlob, pDest };
+ ham_db_insert(m_dbEvents, NULL, &key, &rec, HAM_OVERWRITE);
+
+ // add a sorting key
+ DBEventSortingKey key2 = { contactID, dbe.timestamp, dwEventId };
+ key.size = sizeof(key2); key.data = &key2;
+ rec.size = 1; rec.data = "";
+ ham_db_insert(m_dbEventsSort, NULL, &key, &rec, HAM_OVERWRITE);
+
+ cc->Advance(dwEventId, dbe);
+ ham_key_t keyc = { sizeof(int), &contactID };
+ ham_record_t datac = { sizeof(DBContact), &cc->dbc };
+ ham_db_insert(m_dbContacts, NULL, &keyc, &datac, HAM_OVERWRITE);
+
+ // insert an event into a sub's history too
+ if (ccSub != NULL) {
+ key2.dwContactId = ccSub->contactID;
+ ham_db_insert(m_dbEventsSort, NULL, &key, &rec, HAM_OVERWRITE);
+
+ ccSub->Advance(dwEventId, dbe);
+ datac.data = &ccSub->dbc;
+ keyc.data = &ccSub->contactID;
+ ham_db_insert(m_dbContacts, NULL, &keyc, &datac, HAM_OVERWRITE);
+ }
+
+ // Notify only in safe mode or on really new events
+ if (m_safetyMode)
+ NotifyEventHooks(hEventAddedEvent, contactNotifyID, dwEventId);
+
+ return dwEventId;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::DeleteEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ if (cc == NULL || cc->dbc.dwEventCount == 0)
+ return 1;
+
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ ham_record_t rec = { 0 };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return 1;
+
+ DBEvent *dbe = (DBEvent*)rec.data;
+ DWORD dwSavedContact = dbe->contactID;
+ DBEventSortingKey key2 = { contactID, dbe->timestamp, hDbEvent };
+ ham_db_erase(m_dbEvents, NULL, &key, 0);
+
+ // remove a sorting key
+ key.size = sizeof(key2); key.data = &key2;
+ ham_db_erase(m_dbEventsSort, NULL, &key, 0);
+
+ // remove a sub's history entry too
+ if (contactID != dwSavedContact) {
+ key2.dwContactId = dwSavedContact;
+ ham_db_erase(m_dbEventsSort, NULL, &key, 0);
+ }
+
+ // update a contact
+ key.size = sizeof(int); key.data = &contactID;
+ cc->dbc.dwEventCount--;
+ if (cc->dbc.dwFirstUnread == hDbEvent)
+ FindNextUnread(cc, key2);
+
+ // call notifier while outside mutex
+ NotifyEventHooks(hEventDeletedEvent, contactID, hDbEvent);
+ return 0;
+}
+
+STDMETHODIMP_(LONG) CDbxKV::GetBlobSize(MEVENT hDbEvent)
+{
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ ham_record_t rec = { 0 };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return -1;
+
+ DBEvent *dbe = (DBEvent*)rec.data;
+ return (dbe->dwSignature == DBEVENT_SIGNATURE) ? dbe->cbBlob : 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbei)
+{
+ if (dbei == NULL || dbei->cbSize != sizeof(DBEVENTINFO)) return 1;
+ if (dbei->cbBlob > 0 && dbei->pBlob == NULL) {
+ dbei->cbBlob = 0;
+ return 1;
+ }
+
+ ham_record_t rec = { 0 };
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return 1;
+
+ DBEvent *dbe = (DBEvent*)rec.data;
+ if (dbe->dwSignature != DBEVENT_SIGNATURE)
+ return 1;
+
+ dbei->szModule = GetModuleNameByOfs(dbe->ofsModuleName);
+ dbei->timestamp = dbe->timestamp;
+ dbei->flags = dbe->flags;
+ dbei->eventType = dbe->wEventType;
+ int bytesToCopy = (dbei->cbBlob < dbe->cbBlob) ? dbei->cbBlob : dbe->cbBlob;
+ dbei->cbBlob = dbe->cbBlob;
+ if (bytesToCopy && dbei->pBlob) {
+ BYTE *pSrc = (BYTE*)rec.data + sizeof(DBEvent);
+ if (dbe->flags & DBEF_ENCRYPTED) {
+ dbei->flags &= ~DBEF_ENCRYPTED;
+ size_t len;
+ BYTE* pBlob = (BYTE*)m_crypto->decodeBuffer(pSrc, dbe->cbBlob, &len);
+ if (pBlob == NULL)
+ return 1;
+
+ memcpy(dbei->pBlob, pBlob, bytesToCopy);
+ if (bytesToCopy > (int)len)
+ memset(dbei->pBlob + len, 0, bytesToCopy - len);
+ mir_free(pBlob);
+ }
+ else memcpy(dbei->pBlob, pSrc, bytesToCopy);
+ }
+ return 0;
+}
+
+void CDbxKV::FindNextUnread(DBCachedContact *cc, DBEventSortingKey &key2)
+{
+ ham_record_t rec = { 0 };
+ ham_key_t key = { sizeof(key2), &key2 };
+ key2.dwEventId++;
+
+ cursor_ptr cursor(m_dbEventsSort);
+ if (ham_cursor_find(cursor, &key, &rec, HAM_FIND_GEQ_MATCH) != HAM_SUCCESS)
+ return;
+
+ do {
+ DBEvent *dbe = (DBEvent*)rec.data;
+ if (!dbe->markedRead()) {
+ cc->dbc.dwFirstUnread = key2.dwEventId;
+ cc->dbc.tsFirstUnread = key2.ts;
+ return;
+ }
+ } while (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_NEXT) == 0);
+
+ cc->dbc.dwFirstUnread = cc->dbc.tsFirstUnread = 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::MarkEventRead(MCONTACT contactID, MEVENT hDbEvent)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ if (cc == NULL)
+ return -1;
+
+ ham_record_t rec = { 0 };
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return -1;
+
+ DBEvent *dbe = (DBEvent*)rec.data;
+ if (dbe->dwSignature != DBEVENT_SIGNATURE)
+ return -1;
+
+ if (dbe->markedRead())
+ return dbe->flags;
+
+ DBEventSortingKey key2 = { contactID, dbe->timestamp, hDbEvent };
+
+ dbe->flags |= DBEF_READ;
+ ham_db_insert(m_dbEvents, NULL, &key, &rec, HAM_OVERWRITE);
+
+ FindNextUnread(cc, key2);
+ key.data = &contactID;
+ rec.data = &cc->dbc; rec.size = sizeof(cc->dbc);
+ ham_db_insert(m_dbContacts, NULL, &key, &rec, HAM_OVERWRITE);
+
+ NotifyEventHooks(hEventMarkedRead, contactID, (LPARAM)hDbEvent);
+ return dbe->flags;
+}
+
+STDMETHODIMP_(MCONTACT) CDbxKV::GetEventContact(MEVENT hDbEvent)
+{
+ ham_record_t rec = { 0 };
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return 0;
+
+ DBEvent *dbe = (DBEvent*)rec.data;
+ return (dbe->dwSignature == DBEVENT_SIGNATURE) ? dbe->contactID : INVALID_CONTACT_ID;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::FindFirstEvent(MCONTACT contactID)
+{
+ DBEventSortingKey keyVal = { contactID, 0, 0 };
+ ham_key_t key = { sizeof(keyVal), &keyVal };
+ ham_record_t rec = { 0 };
+
+ if (ham_db_find(m_dbEventsSort, NULL, &key, &rec, HAM_FIND_GT_MATCH) != HAM_SUCCESS)
+ return m_evLast = 0;
+
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ m_tsLast = pKey->ts;
+ return m_evLast = (pKey->dwContactId == contactID) ? pKey->dwEventId : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::FindFirstUnreadEvent(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc == NULL) ? 0 : cc->dbc.dwFirstUnread;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::FindLastEvent(MCONTACT contactID)
+{
+ DBEventSortingKey keyVal = { contactID, 0xFFFFFFFF, 0xFFFFFFFF };
+ ham_key_t key = { sizeof(keyVal), &keyVal };
+ ham_record_t rec = { 0 };
+
+ if (ham_db_find(m_dbEventsSort, NULL, &key, &rec, HAM_FIND_LT_MATCH) != HAM_SUCCESS)
+ return m_evLast = 0;
+
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ m_tsLast = pKey->ts;
+ return m_evLast = (pKey->dwContactId == contactID) ? pKey->dwEventId : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::FindNextEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ DWORD ts;
+ ham_record_t rec = { 0 };
+
+ if (m_evLast != hDbEvent) {
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return 0;
+ m_tsLast = ts = ((DBEvent*)rec.data)->timestamp;
+ }
+ else ts = m_tsLast;
+
+ DBEventSortingKey keyVal = { contactID, ts, hDbEvent };
+ ham_key_t key = { sizeof(keyVal), &keyVal };
+ if (ham_db_find(m_dbEventsSort, NULL, &key, &rec, HAM_FIND_GT_MATCH) != HAM_SUCCESS)
+ return m_evLast = 0;
+
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ m_tsLast = pKey->ts;
+ return m_evLast = (pKey->dwContactId == contactID) ? pKey->dwEventId : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxKV::FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ DWORD ts;
+ ham_record_t rec = { 0 };
+
+ if (m_evLast != hDbEvent) {
+ ham_key_t key = { sizeof(MEVENT), &hDbEvent };
+ if (ham_db_find(m_dbEvents, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) != HAM_SUCCESS)
+ return 0;
+ m_tsLast = ts = ((DBEvent*)rec.data)->timestamp;
+ }
+ else ts = m_tsLast;
+
+ DBEventSortingKey keyVal = { contactID, ts, hDbEvent };
+ ham_key_t key = { sizeof(keyVal), &keyVal };
+ if (ham_db_find(m_dbEventsSort, NULL, &key, &rec, HAM_FIND_LT_MATCH) != HAM_SUCCESS)
+ return m_evLast = 0;
+
+ DBEventSortingKey *pKey = (DBEventSortingKey*)key.data;
+ m_tsLast = pKey->ts;
+ return m_evLast = (pKey->dwContactId == contactID) ? pKey->dwEventId : 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// low-level history cleaner
+
+int CDbxKV::WipeContactHistory(DBContact*)
+{
+ // drop subContact's history if any
+ return 0;
+}
diff --git a/plugins/Dbx_kv/src/dbintf.cpp b/plugins/Dbx_kv/src/dbintf.cpp new file mode 100644 index 0000000000..0b323a125a --- /dev/null +++ b/plugins/Dbx_kv/src/dbintf.cpp @@ -0,0 +1,303 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+static int compareSettings(ham_db_t*, const uint8_t *p1, uint32_t, const uint8_t *p2, uint32_t)
+{
+ DBSettingSortingKey *k1 = (DBSettingSortingKey*)p1, *k2 = (DBSettingSortingKey*)p2;
+ if (k1->dwContactID < k2->dwContactID) return -1;
+ if (k1->dwContactID > k2->dwContactID) return 1;
+
+ if (k1->dwOfsModule < k2->dwOfsModule) return -1;
+ if (k1->dwOfsModule > k2->dwOfsModule) return 1;
+
+ return strcmp(k1->szSettingName, k2->szSettingName);
+}
+
+static int compareEvents(ham_db_t*, const uint8_t *p1, uint32_t, const uint8_t *p2, uint32_t)
+{
+ DBEventSortingKey *k1 = (DBEventSortingKey*)p1, *k2 = (DBEventSortingKey*)p2;
+ if (k1->dwContactId < k2->dwContactId) return -1;
+ if (k1->dwContactId > k2->dwContactId) return 1;
+
+ if (k1->ts < k2->ts) return -1;
+ if (k1->ts > k2->ts) return 1;
+
+ if (k1->dwEventId < k2->dwEventId) return -1;
+ if (k1->dwEventId > k2->dwEventId) return 1;
+
+ return 0;
+}
+
+static int ModCompare(const ModuleName *mn1, const ModuleName *mn2)
+{
+ return strcmp(mn1->name, mn2->name);
+}
+
+static int OfsCompare(const ModuleName *mn1, const ModuleName *mn2)
+{
+ return (mn1->ofs - mn2->ofs);
+}
+
+static int stringCompare2(const char *p1, const char *p2)
+{
+ return strcmp(p1, p2);
+}
+
+CDbxKV::CDbxKV(const TCHAR *tszFileName, int iMode) :
+ m_safetyMode(true),
+ m_bReadOnly((iMode & DBMODE_READONLY) != 0),
+ m_bShared((iMode & DBMODE_SHARED) != 0),
+ m_dwMaxContactId(1),
+ m_lMods(50, ModCompare),
+ m_lOfs(50, OfsCompare),
+ m_lResidentSettings(50, stringCompare2)
+{
+ m_tszProfileName = mir_tstrdup(tszFileName);
+ InitDbInstance(this);
+
+ m_codePage = CallService(MS_LANGPACK_GETCODEPAGE, 0, 0);
+ m_hModHeap = HeapCreate(0, 0, 0);
+}
+
+CDbxKV::~CDbxKV()
+{
+ // destroy modules
+ HeapDestroy(m_hModHeap);
+
+ // automatically closes all tables
+ ham_env_close(m_pMdbEnv, HAM_AUTO_CLEANUP);
+
+ DestroyServiceFunction(hService);
+ UnhookEvent(hHook);
+
+ if (m_crypto)
+ m_crypto->destroy();
+
+ DestroyHookableEvent(hContactDeletedEvent);
+ DestroyHookableEvent(hContactAddedEvent);
+ DestroyHookableEvent(hSettingChangeEvent);
+ DestroyHookableEvent(hEventMarkedRead);
+
+ DestroyHookableEvent(hEventAddedEvent);
+ DestroyHookableEvent(hEventDeletedEvent);
+ DestroyHookableEvent(hEventFilterAddedEvent);
+
+ DestroyDbInstance(this);
+ mir_free(m_tszProfileName);
+}
+
+int CDbxKV::Load(bool bSkipInit)
+{
+ int mode = HAM_ENABLE_FSYNC | HAM_DISABLE_RECOVERY;
+ if (m_bReadOnly)
+ mode += HAM_READ_ONLY;
+
+ if (ham_env_open(&m_pMdbEnv, _T2A(m_tszProfileName), mode, NULL) != HAM_SUCCESS)
+ return EGROKPRF_CANTREAD;
+
+ if (!bSkipInit) {
+ int iFlags = (m_bReadOnly) ? HAM_READ_ONLY : 0;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbGlobal, 1, iFlags, 0)) return EGROKPRF_DAMAGED;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbContacts, 2, iFlags, 0)) return EGROKPRF_DAMAGED;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbModules, 3, iFlags, 0)) return EGROKPRF_DAMAGED;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbEvents, 4, iFlags, 0)) return EGROKPRF_DAMAGED;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbEventsSort, 5, iFlags, 0)) return EGROKPRF_DAMAGED;
+ if (ham_env_open_db(m_pMdbEnv, &m_dbSettings, 6, iFlags, 0)) return EGROKPRF_DAMAGED;
+
+ ham_db_set_compare_func(m_dbEventsSort, compareEvents);
+ ham_db_set_compare_func(m_dbSettings, compareSettings);
+
+ DWORD keyVal = 1;
+ ham_key_t key = { sizeof(DWORD), &keyVal };
+ ham_record_t rec = { 0 };
+ if (ham_db_find(m_dbGlobal, NULL, &key, &rec, HAM_FIND_EXACT_MATCH) == HAM_SUCCESS) {
+ DBHeader *hdr = (DBHeader*)rec.data;
+ if (hdr->dwSignature != DBHEADER_SIGNATURE)
+ DatabaseCorruption(NULL);
+
+ memcpy(&m_header, rec.data, sizeof(m_header));
+ }
+ else {
+ m_header.dwSignature = DBHEADER_SIGNATURE;
+ m_header.dwVersion = 1;
+ rec.data = &m_header; rec.size = sizeof(m_header);
+ ham_db_insert(m_dbGlobal, NULL, &key, &rec, HAM_OVERWRITE);
+
+ keyVal = 0;
+ DBContact dbc = { DBCONTACT_SIGNATURE, 0, 0, 0 };
+ rec.data = &dbc; rec.size = sizeof(dbc);
+ ham_db_insert(m_dbContacts, NULL, &key, &rec, HAM_OVERWRITE);
+ }
+
+ if (InitModuleNames()) return EGROKPRF_CANTREAD;
+ if (InitCrypt()) return EGROKPRF_CANTREAD;
+
+ // everything is ok, go on
+ if (!m_bReadOnly) {
+ // we don't need events in the service mode
+ if (ServiceExists(MS_DB_SETSAFETYMODE)) {
+ hContactDeletedEvent = CreateHookableEvent(ME_DB_CONTACT_DELETED);
+ hContactAddedEvent = CreateHookableEvent(ME_DB_CONTACT_ADDED);
+ hSettingChangeEvent = CreateHookableEvent(ME_DB_CONTACT_SETTINGCHANGED);
+ hEventMarkedRead = CreateHookableEvent(ME_DB_EVENT_MARKED_READ);
+
+ hEventAddedEvent = CreateHookableEvent(ME_DB_EVENT_ADDED);
+ hEventDeletedEvent = CreateHookableEvent(ME_DB_EVENT_DELETED);
+ hEventFilterAddedEvent = CreateHookableEvent(ME_DB_EVENT_FILTER_ADD);
+ }
+ }
+
+ FillContacts();
+ }
+
+ return ERROR_SUCCESS;
+}
+
+int CDbxKV::Create(void)
+{
+ int flags = HAM_ENABLE_FSYNC | HAM_DISABLE_RECOVERY;
+ if (ham_env_create(&m_pMdbEnv, _T2A(m_tszProfileName), flags, 0664, NULL) != HAM_SUCCESS)
+ return EGROKPRF_CANTREAD;
+
+ ham_parameter_t paramPrimKey32[] = { { HAM_PARAM_KEY_TYPE, HAM_TYPE_UINT32 }, { 0, 0 } };
+ if (ham_env_create_db(m_pMdbEnv, &m_dbGlobal, 1, 0, paramPrimKey32)) return EGROKPRF_DAMAGED;
+ if (ham_env_create_db(m_pMdbEnv, &m_dbContacts, 2, 0, paramPrimKey32)) return EGROKPRF_DAMAGED;
+ if (ham_env_create_db(m_pMdbEnv, &m_dbModules, 3, 0, paramPrimKey32)) return EGROKPRF_DAMAGED;
+ if (ham_env_create_db(m_pMdbEnv, &m_dbEvents, 4, 0, paramPrimKey32)) return EGROKPRF_DAMAGED;
+
+ ham_parameter_t paramEventsPrimKey[] = {
+ { HAM_PARAM_KEY_TYPE, HAM_TYPE_CUSTOM },
+ { HAM_PARAM_KEY_SIZE, sizeof(DBEventSortingKey) },
+ { 0, 0 } };
+ if (ham_env_create_db(m_pMdbEnv, &m_dbEventsSort, 5, 0, paramEventsPrimKey)) return EGROKPRF_DAMAGED;
+ ham_db_set_compare_func(m_dbEventsSort, compareEvents);
+
+ ham_parameter_t paramSettingsPrimKey[] = {
+ { HAM_PARAM_KEY_TYPE, HAM_TYPE_CUSTOM },
+ { 0, 0 } };
+ if (ham_env_create_db(m_pMdbEnv, &m_dbSettings, 6, 0, paramSettingsPrimKey)) return EGROKPRF_DAMAGED;
+ ham_db_set_compare_func(m_dbSettings, compareSettings);
+
+ return 0;
+}
+
+int CDbxKV::Check(void)
+{
+ HANDLE hFile = CreateFile(m_tszProfileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return EGROKPRF_CANTREAD;
+
+ DWORD dummy = 0;
+ char buf[32];
+ if (!ReadFile(hFile, buf, sizeof(buf), &dummy, NULL)) {
+ CloseHandle(hFile);
+ return EGROKPRF_CANTREAD;
+ }
+
+ CloseHandle(hFile);
+ return (memcmp(buf + 16, "HAM\x00", 4)) ? EGROKPRF_UNKHEADER : 0;
+}
+
+int CDbxKV::PrepareCheck(int*)
+{
+ InitModuleNames();
+ return InitCrypt();
+}
+
+STDMETHODIMP_(void) CDbxKV::SetCacheSafetyMode(BOOL bIsSet)
+{
+ mir_cslock lck(m_csDbAccess);
+ m_safetyMode = bIsSet != 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static DWORD DatabaseCorrupted = 0;
+static const TCHAR *msg = NULL;
+static DWORD dwErr = 0;
+static TCHAR tszPanic[] = LPGENT("Miranda has detected corruption in your database. This corruption may be fixed by DbChecker plugin. Please download it from http://miranda-ng.org/p/DbChecker/. Miranda will now shut down.");
+
+void __cdecl dbpanic(void *)
+{
+ if (msg) {
+ if (dwErr == ERROR_DISK_FULL)
+ msg = TranslateT("Disk is full. Miranda will now shut down.");
+
+ TCHAR err[256];
+ mir_sntprintf(err, SIZEOF(err), msg, TranslateT("Database failure. Miranda will now shut down."), dwErr);
+
+ MessageBox(0, err, TranslateT("Database Error"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
+ }
+ else MessageBox(0, TranslateTS(tszPanic), TranslateT("Database Panic"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
+ TerminateProcess(GetCurrentProcess(), 255);
+}
+
+void CDbxKV::DatabaseCorruption(const TCHAR *text)
+{
+ int kill = 0;
+
+ mir_cslockfull lck(m_csDbAccess);
+ if (DatabaseCorrupted == 0) {
+ DatabaseCorrupted++;
+ kill++;
+ msg = text;
+ dwErr = GetLastError();
+ }
+ else {
+ /* db is already corrupted, someone else is dealing with it, wait here
+ so that we don't do any more damage */
+ Sleep(INFINITE);
+ return;
+ }
+ lck.unlock();
+
+ if (kill) {
+ _beginthread(dbpanic, 0, NULL);
+ Sleep(INFINITE);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MIDatabaseChecker
+
+typedef int (CDbxKV::*CheckWorker)(int);
+
+int CDbxKV::Start(DBCHeckCallback *callback)
+{
+ cb = callback;
+ return ERROR_SUCCESS;
+}
+
+int CDbxKV::CheckDb(int, int)
+{
+ return ERROR_OUT_OF_PAPER;
+
+ // return (this->*Workers[phase])(firstTime);
+}
+
+void CDbxKV::Destroy()
+{
+ delete this;
+}
diff --git a/plugins/Dbx_kv/src/dbintf.h b/plugins/Dbx_kv/src/dbintf.h new file mode 100644 index 0000000000..4a65db7fda --- /dev/null +++ b/plugins/Dbx_kv/src/dbintf.h @@ -0,0 +1,306 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#define OWN_CACHED_CONTACT
+
+#include <m_db_int.h>
+
+/* tree diagram
+
+DBHeader
+|-->end of file (plain offset)
+|-->first contact (DBContact)
+| |-->next contact (DBContact)
+| | \--> ...
+| |-->first settings (DBContactSettings)
+| | |-->next settings (DBContactSettings)
+| | | \--> ...
+| | \-->module name (DBModuleName)
+| \-->first/last/firstunread event
+|-->user contact (DBContact)
+| |-->next contact = NULL
+| |-->first settings as above
+| \-->first/last/firstunread event as above
+\-->first module name (DBModuleName)
+\-->next module name (DBModuleName)
+\--> ...
+*/
+
+#define DBMODE_SHARED 0x0001
+#define DBMODE_READONLY 0x0002
+
+#define DBVT_ENCRYPTED 250
+#define DBVT_UNENCRYPTED 251
+
+#define MARKED_READ (DBEF_READ | DBEF_SENT)
+
+struct ModuleName
+{
+ char *name;
+ DWORD ofs;
+};
+
+#include <pshpack1.h>
+
+#define DBHEADER_SIGNATURE 0x40DECADEu
+struct DBHeader
+{
+ DWORD dwSignature;
+ DWORD dwVersion; // database format version
+};
+
+#define DBCONTACT_SIGNATURE 0x43DECADEu
+struct DBContact
+{
+ DWORD dwSignature;
+ DWORD dwEventCount; // number of events in the chain for this contact
+ DWORD tsFirstUnread;
+ DWORD dwFirstUnread;
+};
+
+#define DBMODULENAME_SIGNATURE 0x4DDECADEu
+struct DBModuleName
+{
+ DWORD dwSignature;
+ BYTE cbName; // number of characters in this module name
+ char name[1]; // name, no nul terminator
+};
+
+#define DBEVENT_SIGNATURE 0x45DECADEu
+struct DBEvent
+{
+ DWORD dwSignature;
+ MCONTACT contactID; // a contact this event belongs to
+ DWORD ofsModuleName; // offset to a DBModuleName struct of the name of
+ DWORD timestamp; // seconds since 00:00:00 01/01/1970
+ DWORD flags; // see m_database.h, db/event/add
+ WORD wEventType; // module-defined event type
+ WORD cbBlob; // number of bytes in the blob
+
+ bool __forceinline markedRead() const
+ {
+ return (flags & MARKED_READ) != 0;
+ }
+};
+
+#include <poppack.h>
+
+struct DBEventSortingKey
+{
+ DWORD dwContactId, ts, dwEventId;
+};
+
+struct DBSettingSortingKey
+{
+ DWORD dwContactID;
+ DWORD dwOfsModule;
+ char szSettingName[100];
+};
+
+struct DBCachedContact : public DBCachedContactBase
+{
+ void Advance(DWORD id, DBEvent &dbe);
+
+ DBContact dbc;
+};
+
+struct CDbxKV : public MIDatabase, public MIDatabaseChecker, public MZeroedObject
+{
+ CDbxKV(const TCHAR *tszFileName, int mode);
+ ~CDbxKV();
+
+ int Load(bool bSkipInit);
+ int Create(void);
+ int Check(void);
+
+ void DatabaseCorruption(const TCHAR *ptszText);
+
+ void ToggleEncryption(void);
+ void StoreKey(void);
+ void SetPassword(const TCHAR *ptszPassword);
+ void UpdateMenuItem(void);
+
+ int PrepareCheck(int*);
+
+ __forceinline LPSTR GetMenuTitle() const { return m_bUsesPassword ? LPGEN("Change/remove password") : LPGEN("Set password"); }
+
+ __forceinline bool isEncrypted() const { return m_bEncrypted; }
+ __forceinline bool usesPassword() const { return m_bUsesPassword; }
+
+public:
+ STDMETHODIMP_(void) SetCacheSafetyMode(BOOL);
+
+ STDMETHODIMP_(LONG) GetContactCount(void);
+ STDMETHODIMP_(MCONTACT) FindFirstContact(const char *szProto = NULL);
+ STDMETHODIMP_(MCONTACT) FindNextContact(MCONTACT contactID, const char *szProto = NULL);
+ STDMETHODIMP_(LONG) DeleteContact(MCONTACT contactID);
+ STDMETHODIMP_(MCONTACT) AddContact(void);
+ STDMETHODIMP_(BOOL) IsDbContact(MCONTACT contactID);
+ STDMETHODIMP_(LONG) GetContactSize(void);
+
+ STDMETHODIMP_(LONG) GetEventCount(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) AddEvent(MCONTACT contactID, DBEVENTINFO *dbe);
+ STDMETHODIMP_(BOOL) DeleteEvent(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(LONG) GetBlobSize(MEVENT hDbEvent);
+ STDMETHODIMP_(BOOL) GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbe);
+ STDMETHODIMP_(BOOL) MarkEventRead(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(MCONTACT) GetEventContact(MEVENT hDbEvent);
+ STDMETHODIMP_(MEVENT) FindFirstEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindFirstUnreadEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindLastEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindNextEvent(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(MEVENT) FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent);
+
+ STDMETHODIMP_(BOOL) EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam);
+
+ STDMETHODIMP_(BOOL) GetContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv);
+ STDMETHODIMP_(BOOL) GetContactSettingStr(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv);
+ STDMETHODIMP_(BOOL) GetContactSettingStatic(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv);
+ STDMETHODIMP_(BOOL) FreeVariant(DBVARIANT *dbv);
+ STDMETHODIMP_(BOOL) WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws);
+ STDMETHODIMP_(BOOL) DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting);
+ STDMETHODIMP_(BOOL) EnumContactSettings(MCONTACT contactID, DBCONTACTENUMSETTINGS *dbces);
+ STDMETHODIMP_(BOOL) SetSettingResident(BOOL bIsResident, const char *pszSettingName);
+ STDMETHODIMP_(BOOL) EnumResidentSettings(DBMODULEENUMPROC pFunc, void *pParam);
+ STDMETHODIMP_(BOOL) IsSettingEncrypted(LPCSTR szModule, LPCSTR szSetting);
+
+ STDMETHODIMP_(BOOL) MetaDetouchSub(DBCachedContact *cc, int nSub);
+ STDMETHODIMP_(BOOL) MetaSetDefault(DBCachedContact *cc);
+ STDMETHODIMP_(BOOL) MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub);
+ STDMETHODIMP_(BOOL) MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub);
+
+protected:
+ STDMETHODIMP_(BOOL) Start(DBCHeckCallback *callback);
+ STDMETHODIMP_(BOOL) CheckDb(int phase, int firstTime);
+ STDMETHODIMP_(VOID) Destroy();
+
+protected:
+ void InvalidateSettingsGroupOfsCacheEntry(DWORD) {}
+ int WorkInitialCheckHeaders(void);
+
+ void FillContacts(void);
+
+public: // Check functions
+ int WorkInitialChecks(int);
+ int WorkModuleChain(int);
+ int WorkUser(int);
+ int WorkContactChain(int);
+ int WorkAggressive(int);
+ int WorkFinalTasks(int);
+
+protected:
+ TCHAR* m_tszProfileName;
+ bool m_safetyMode, m_bReadOnly, m_bShared, m_bEncrypted, m_bUsesPassword;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // database stuff
+public:
+ MICryptoEngine *m_crypto;
+
+protected:
+ ham_env_t *m_pMdbEnv;
+ ham_db_t *m_dbGlobal;
+ DBHeader m_header;
+
+ HANDLE hSettingChangeEvent, hContactDeletedEvent, hContactAddedEvent, hEventMarkedRead;
+
+ mir_cs m_csDbAccess;
+
+ int CheckProto(DBCachedContact *cc, const char *proto);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // settings
+
+ ham_db_t *m_dbSettings;
+ int m_codePage;
+ HANDLE hService, hHook;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // contacts
+
+ ham_db_t *m_dbContacts;
+ int m_contactCount, m_dwMaxContactId;
+
+ int WipeContactHistory(DBContact *dbc);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // events
+
+ ham_db_t *m_dbEvents, *m_dbEventsSort;
+ DWORD m_dwMaxEventId, m_tsLast;
+ MEVENT m_evLast;
+
+ void FindNextUnread(DBCachedContact *cc, DBEventSortingKey &key2);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // modules
+
+ ham_db_t *m_dbModules;
+ HANDLE m_hModHeap;
+ LIST<ModuleName> m_lMods, m_lOfs;
+ LIST<char> m_lResidentSettings;
+ HANDLE hEventAddedEvent, hEventDeletedEvent, hEventFilterAddedEvent;
+ MCONTACT m_hLastCachedContact;
+ int m_maxModuleID;
+
+ void AddToList(char *name, DWORD ofs);
+ DWORD FindExistingModuleNameOfs(const char *szName);
+ int InitModuleNames(void);
+ DWORD GetModuleNameOfs(const char *szName);
+ char* GetModuleNameByOfs(DWORD ofs);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // checker
+
+ int PeekSegment(DWORD ofs, PVOID buf, int cbBytes);
+ int ReadSegment(DWORD ofs, PVOID buf, int cbBytes);
+ int ReadWrittenSegment(DWORD ofs, PVOID buf, int cbBytes);
+ int SignatureValid(DWORD ofs, DWORD dwSignature);
+ void FreeModuleChain();
+
+ DWORD ConvertModuleNameOfs(DWORD ofsOld);
+ void ConvertOldEvent(DBEvent*& dbei);
+
+ int GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic);
+ int WorkSettingsChain(DBContact *dbc, int firstTime);
+ int WorkEventChain(DWORD ofsContact, DBContact *dbc, int firstTime);
+
+ DWORD WriteSegment(DWORD ofs, PVOID buf, int cbBytes);
+ DWORD WriteEvent(DBEvent *dbe);
+ DWORD PeekEvent(DWORD ofs, DWORD dwContactID, DBEvent &dbe);
+ void WriteOfsNextToPrevious(DWORD ofsPrev, DBContact *dbc, DWORD ofsNext);
+ void FinishUp(DWORD ofsLast, DBContact *dbc);
+
+ DBCHeckCallback *cb;
+ DWORD sourceFileSize, ofsAggrCur;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // encryption
+
+ int InitCrypt(void);
+ void ToggleEventsEncryption(MCONTACT contactID);
+ void ToggleSettingsEncryption(MCONTACT contactID);
+
+ void InitDialogs();
+ bool EnterPassword(const BYTE *pKey, const size_t keyLen);
+};
diff --git a/plugins/Dbx_kv/src/dbmodulechain.cpp b/plugins/Dbx_kv/src/dbmodulechain.cpp new file mode 100644 index 0000000000..d1491fc68a --- /dev/null +++ b/plugins/Dbx_kv/src/dbmodulechain.cpp @@ -0,0 +1,132 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+void CDbxKV::AddToList(char *name, DWORD ofs)
+{
+ ModuleName *mn = (ModuleName*)HeapAlloc(m_hModHeap, 0, sizeof(ModuleName));
+ mn->name = name;
+ mn->ofs = ofs;
+
+ if (m_lMods.getIndex(mn) != -1)
+ DatabaseCorruption(_T("%s (Module Name not unique)"));
+ m_lMods.insert(mn);
+
+ if (m_lOfs.getIndex(mn) != -1)
+ DatabaseCorruption(_T("%s (Module Offset not unique)"));
+ m_lOfs.insert(mn);
+}
+
+int CDbxKV::InitModuleNames(void)
+{
+ m_maxModuleID = 0;
+
+ ham_key_t key = { 0 };
+ ham_record_t rec = { 0 };
+ cursor_ptr cursor(m_dbModules);
+ if (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_FIRST) != HAM_SUCCESS)
+ return 0;
+
+ do {
+ DBModuleName *pmod = (DBModuleName*)rec.data;
+ if (pmod->dwSignature != DBMODULENAME_SIGNATURE)
+ DatabaseCorruption(NULL);
+
+ char *pVal = (char*)HeapAlloc(m_hModHeap, 0, pmod->cbName+1);
+ memcpy(pVal, pmod->name, pmod->cbName);
+ pVal[pmod->cbName] = 0;
+
+ int moduleId = *(int*)key.data;
+ AddToList(pVal, moduleId);
+
+ if (moduleId > m_maxModuleID)
+ m_maxModuleID = moduleId;
+ } while (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_NEXT) == 0);
+
+ return 0;
+}
+
+DWORD CDbxKV::FindExistingModuleNameOfs(const char *szName)
+{
+ ModuleName mn = { (char*)szName, 0 };
+
+ int index = m_lMods.getIndex(&mn);
+ if (index != -1)
+ return m_lMods[index]->ofs;
+
+ return 0;
+}
+
+// will create the offset if it needs to
+DWORD CDbxKV::GetModuleNameOfs(const char *szName)
+{
+ DWORD ofsExisting = FindExistingModuleNameOfs(szName);
+ if (ofsExisting)
+ return ofsExisting;
+
+ if (m_bReadOnly)
+ return 0;
+
+ int nameLen = (int)strlen(szName);
+
+ // need to create the module name
+ int newIdx = ++m_maxModuleID;
+ DBModuleName *pmod = (DBModuleName*)_alloca(sizeof(DBModuleName) + nameLen);
+ pmod->dwSignature = DBMODULENAME_SIGNATURE;
+ pmod->cbName = (char)nameLen;
+ strcpy(pmod->name, szName);
+
+ ham_key_t key = { sizeof(int), &newIdx };
+ ham_record_t rec = { sizeof(DBModuleName) + nameLen, pmod };
+ ham_db_insert(m_dbModules, NULL, &key, &rec, HAM_OVERWRITE);
+
+ // add to cache
+ char *mod = (char*)HeapAlloc(m_hModHeap, 0, nameLen + 1);
+ strcpy(mod, szName);
+ AddToList(mod, newIdx);
+
+ // quit
+ return -1;
+}
+
+char* CDbxKV::GetModuleNameByOfs(DWORD ofs)
+{
+ ModuleName mn = { NULL, ofs };
+ int index = m_lOfs.getIndex(&mn);
+ if (index != -1)
+ return m_lOfs[index]->name;
+
+ return NULL;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam)
+{
+ for (int i = 0; i < m_lMods.getCount(); i++) {
+ ModuleName *pmn = m_lMods[i];
+ int ret = pFunc(pmn->name, pmn->ofs, (LPARAM)pParam);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
diff --git a/plugins/Dbx_kv/src/dbsettings.cpp b/plugins/Dbx_kv/src/dbsettings.cpp new file mode 100644 index 0000000000..0ec5a21667 --- /dev/null +++ b/plugins/Dbx_kv/src/dbsettings.cpp @@ -0,0 +1,605 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+#define VLT(n) ((n == DBVT_UTF8 || n == DBVT_ENCRYPTED)?DBVT_ASCIIZ:n)
+
+BOOL CDbxKV::IsSettingEncrypted(LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!_strnicmp(szSetting, "password", 8)) return true;
+ if (!strcmp(szSetting, "NLProxyAuthPassword")) return true;
+ if (!strcmp(szSetting, "LNPassword")) return true;
+ if (!strcmp(szSetting, "FileProxyPassword")) return true;
+ if (!strcmp(szSetting, "TokenSecret")) return true;
+
+ if (!strcmp(szModule, "SecureIM")) {
+ if (!strcmp(szSetting, "pgp")) return true;
+ if (!strcmp(szSetting, "pgpPrivKey")) return true;
+ }
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool ValidLookupName(LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!strcmp(szModule, META_PROTO))
+ return strcmp(szSetting, "IsSubcontact") && strcmp(szSetting, "ParentMetaID");
+
+ if (!strcmp(szModule, "Ignore"))
+ return false;
+
+ return true;
+}
+
+int CDbxKV::GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic)
+{
+ if (szSetting == NULL || szModule == NULL)
+ return 1;
+
+ // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name
+ int settingNameLen = (int)strlen(szSetting);
+ int moduleNameLen = (int)strlen(szModule);
+ if (settingNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("GetContactSettingWorker() got a > 255 setting name length. \n");
+#endif
+ return 1;
+ }
+ if (moduleNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("GetContactSettingWorker() got a > 255 module name length. \n");
+#endif
+ return 1;
+ }
+
+ mir_cslock lck(m_csDbAccess);
+
+LBL_Seek:
+ char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen);
+
+ DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 0);
+ if (pCachedValue != NULL) {
+ if (pCachedValue->type == DBVT_ASCIIZ || pCachedValue->type == DBVT_UTF8) {
+ int cbOrigLen = dbv->cchVal;
+ char *cbOrigPtr = dbv->pszVal;
+ memcpy(dbv, pCachedValue, sizeof(DBVARIANT));
+ if (isStatic) {
+ int cbLen = 0;
+ if (pCachedValue->pszVal != NULL)
+ cbLen = (int)strlen(pCachedValue->pszVal);
+
+ cbOrigLen--;
+ dbv->pszVal = cbOrigPtr;
+ if (cbLen < cbOrigLen)
+ cbOrigLen = cbLen;
+ memcpy(dbv->pszVal, pCachedValue->pszVal, cbOrigLen);
+ dbv->pszVal[cbOrigLen] = 0;
+ dbv->cchVal = cbLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(strlen(pCachedValue->pszVal) + 1);
+ strcpy(dbv->pszVal, pCachedValue->pszVal);
+ }
+ }
+ else memcpy(dbv, pCachedValue, sizeof(DBVARIANT));
+
+ return (pCachedValue->type == DBVT_DELETED) ? 1 : 0;
+ }
+
+ // never look db for the resident variable
+ if (szCachedSettingName[-1] != 0)
+ return 1;
+
+ DBCachedContact *cc = (contactID) ? m_cache->GetCachedContact(contactID) : NULL;
+
+ DBSettingSortingKey keySearch;
+ keySearch.dwContactID = contactID;
+ keySearch.dwOfsModule = GetModuleNameOfs(szModule);
+ strncpy_s(keySearch.szSettingName, szSetting, _TRUNCATE);
+
+ ham_key_t key = { 2 * sizeof(DWORD) + settingNameLen, &keySearch };
+ ham_record_t rec = { 0 };
+ if (ham_db_find(m_dbSettings, NULL, &key, &rec, 0)) {
+ // try to get the missing mc setting from the active sub
+ if (cc && cc->IsMeta() && ValidLookupName(szModule, szSetting)) {
+ if (contactID = db_mc_getDefault(contactID)) {
+ if (szModule = GetContactProto(contactID)) {
+ moduleNameLen = (int)strlen(szModule);
+ goto LBL_Seek;
+ }
+ }
+ }
+ return 1;
+ }
+
+ BYTE *pBlob = (BYTE*)rec.data;
+ if (isStatic && (pBlob[0] & DBVTF_VARIABLELENGTH) && VLT(dbv->type) != VLT(pBlob[0]))
+ return 1;
+
+ int varLen;
+ BYTE iType = dbv->type = pBlob[0]; pBlob++;
+ switch (iType) {
+ case DBVT_DELETED: /* this setting is deleted */
+ dbv->type = DBVT_DELETED;
+ return 2;
+
+ case DBVT_BYTE: dbv->bVal = *pBlob; break;
+ case DBVT_WORD: dbv->wVal = *(WORD*)pBlob; break;
+ case DBVT_DWORD: dbv->dVal = *(DWORD*)pBlob; break;
+
+ case DBVT_UTF8:
+ case DBVT_ASCIIZ:
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+ if (isStatic) {
+ dbv->cchVal--;
+ if (varLen < dbv->cchVal)
+ dbv->cchVal = varLen;
+ memmove(dbv->pszVal, pBlob, dbv->cchVal); // decode
+ dbv->pszVal[dbv->cchVal] = 0;
+ dbv->cchVal = varLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(1 + varLen);
+ memmove(dbv->pszVal, pBlob, varLen);
+ dbv->pszVal[varLen] = 0;
+ }
+ break;
+
+ case DBVT_BLOB:
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+ if (isStatic) {
+ if (varLen < dbv->cpbVal)
+ dbv->cpbVal = varLen;
+ memmove(dbv->pbVal, pBlob, dbv->cpbVal);
+ }
+ else {
+ dbv->pbVal = (BYTE *)mir_alloc(varLen);
+ memmove(dbv->pbVal, pBlob, varLen);
+ }
+ dbv->cpbVal = varLen;
+ break;
+
+ case DBVT_ENCRYPTED:
+ if (m_crypto == NULL)
+ return 1;
+
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+
+ size_t realLen;
+ ptrA decoded(m_crypto->decodeString(pBlob, varLen, &realLen));
+ if (decoded == NULL)
+ return 1;
+
+ varLen = (WORD)realLen;
+ dbv->type = DBVT_UTF8;
+ if (isStatic) {
+ dbv->cchVal--;
+ if (varLen < dbv->cchVal)
+ dbv->cchVal = varLen;
+ memmove(dbv->pszVal, decoded, dbv->cchVal);
+ dbv->pszVal[dbv->cchVal] = 0;
+ dbv->cchVal = varLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(1 + varLen);
+ memmove(dbv->pszVal, decoded, varLen);
+ dbv->pszVal[varLen] = 0;
+ }
+ break;
+ }
+
+ /**** add to cache **********************/
+ if (iType != DBVT_BLOB && iType != DBVT_ENCRYPTED) {
+ DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1);
+ if (pCachedValue != NULL)
+ m_cache->SetCachedVariant(dbv, pCachedValue);
+ }
+
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::GetContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv)
+{
+ dbv->type = 0;
+ if (GetContactSettingWorker(contactID, szModule, szSetting, dbv, 0))
+ return 1;
+
+ if (dbv->type == DBVT_UTF8) {
+ WCHAR *tmp = NULL;
+ char *p = NEWSTR_ALLOCA(dbv->pszVal);
+ if (mir_utf8decode(p, &tmp) != NULL) {
+ BOOL bUsed = FALSE;
+ int result = WideCharToMultiByte(m_codePage, WC_NO_BEST_FIT_CHARS, tmp, -1, NULL, 0, NULL, &bUsed);
+
+ mir_free(dbv->pszVal);
+
+ if (bUsed || result == 0) {
+ dbv->type = DBVT_WCHAR;
+ dbv->pwszVal = tmp;
+ }
+ else {
+ dbv->type = DBVT_ASCIIZ;
+ dbv->pszVal = (char *)mir_alloc(result);
+ WideCharToMultiByte(m_codePage, WC_NO_BEST_FIT_CHARS, tmp, -1, dbv->pszVal, result, NULL, NULL);
+ mir_free(tmp);
+ }
+ }
+ else {
+ dbv->type = DBVT_ASCIIZ;
+ mir_free(tmp);
+ }
+ }
+
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::GetContactSettingStr(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv)
+{
+ int iSaveType = dbv->type;
+
+ if (GetContactSettingWorker(contactID, szModule, szSetting, dbv, 0))
+ return 1;
+
+ if (iSaveType == 0 || iSaveType == dbv->type)
+ return 0;
+
+ if (dbv->type != DBVT_ASCIIZ && dbv->type != DBVT_UTF8)
+ return 1;
+
+ if (iSaveType == DBVT_WCHAR) {
+ if (dbv->type != DBVT_UTF8) {
+ int len = MultiByteToWideChar(CP_ACP, 0, dbv->pszVal, -1, NULL, 0);
+ wchar_t *wszResult = (wchar_t*)mir_alloc((len + 1)*sizeof(wchar_t));
+ if (wszResult == NULL)
+ return 1;
+
+ MultiByteToWideChar(CP_ACP, 0, dbv->pszVal, -1, wszResult, len);
+ wszResult[len] = 0;
+ mir_free(dbv->pszVal);
+ dbv->pwszVal = wszResult;
+ }
+ else {
+ char* savePtr = NEWSTR_ALLOCA(dbv->pszVal);
+ mir_free(dbv->pszVal);
+ if (!mir_utf8decode(savePtr, &dbv->pwszVal))
+ return 1;
+ }
+ }
+ else if (iSaveType == DBVT_UTF8) {
+ char* tmpBuf = mir_utf8encode(dbv->pszVal);
+ if (tmpBuf == NULL)
+ return 1;
+
+ mir_free(dbv->pszVal);
+ dbv->pszVal = tmpBuf;
+ }
+ else if (iSaveType == DBVT_ASCIIZ)
+ mir_utf8decode(dbv->pszVal, NULL);
+
+ dbv->type = iSaveType;
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::GetContactSettingStatic(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv)
+{
+ if (GetContactSettingWorker(contactID, szModule, szSetting, dbv, 1))
+ return 1;
+
+ if (dbv->type == DBVT_UTF8) {
+ mir_utf8decode(dbv->pszVal, NULL);
+ dbv->type = DBVT_ASCIIZ;
+ }
+
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::FreeVariant(DBVARIANT *dbv)
+{
+ if (dbv == 0) return 1;
+
+ switch (dbv->type) {
+ case DBVT_ASCIIZ:
+ case DBVT_UTF8:
+ case DBVT_WCHAR:
+ if (dbv->pszVal) mir_free(dbv->pszVal);
+ dbv->pszVal = 0;
+ break;
+ case DBVT_BLOB:
+ if (dbv->pbVal) mir_free(dbv->pbVal);
+ dbv->pbVal = 0;
+ break;
+ }
+ dbv->type = 0;
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::SetSettingResident(BOOL bIsResident, const char *pszSettingName)
+{
+ char *szSetting = m_cache->GetCachedSetting(NULL, pszSettingName, 0, (int)strlen(pszSettingName));
+ szSetting[-1] = (char)bIsResident;
+
+ mir_cslock lck(m_csDbAccess);
+ int idx = m_lResidentSettings.getIndex(szSetting);
+ if (idx == -1) {
+ if (bIsResident)
+ m_lResidentSettings.insert(szSetting);
+ }
+ else if (!bIsResident)
+ m_lResidentSettings.remove(idx);
+
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws)
+{
+ if (dbcws == NULL || dbcws->szSetting == NULL || dbcws->szModule == NULL || m_bReadOnly)
+ return 1;
+
+ // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name
+ int settingNameLen = (int)strlen(dbcws->szSetting);
+ int moduleNameLen = (int)strlen(dbcws->szModule);
+ if (settingNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("WriteContactSetting() got a > 255 setting name length. \n");
+#endif
+ return 1;
+ }
+ if (moduleNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("WriteContactSetting() got a > 255 module name length. \n");
+#endif
+ return 1;
+ }
+
+ // used for notifications
+ DBCONTACTWRITESETTING dbcwNotif = *dbcws;
+ if (dbcwNotif.value.type == DBVT_WCHAR) {
+ if (dbcwNotif.value.pszVal != NULL) {
+ char* val = mir_utf8encodeW(dbcwNotif.value.pwszVal);
+ if (val == NULL)
+ return 1;
+
+ dbcwNotif.value.pszVal = (char*)alloca(strlen(val) + 1);
+ strcpy(dbcwNotif.value.pszVal, val);
+ mir_free(val);
+ dbcwNotif.value.type = DBVT_UTF8;
+ }
+ else return 1;
+ }
+
+ if (dbcwNotif.szModule == NULL || dbcwNotif.szSetting == NULL)
+ return 1;
+
+ DBCONTACTWRITESETTING dbcwWork = dbcwNotif;
+
+ mir_ptr<BYTE> pEncoded(NULL);
+ bool bIsEncrypted = false;
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: case DBVT_WORD: case DBVT_DWORD:
+ break;
+
+ case DBVT_ASCIIZ: case DBVT_UTF8:
+ bIsEncrypted = m_bEncrypted || IsSettingEncrypted(dbcws->szModule, dbcws->szSetting);
+ LBL_WriteString:
+ if (dbcwWork.value.pszVal == NULL)
+ return 1;
+ dbcwWork.value.cchVal = (WORD)strlen(dbcwWork.value.pszVal);
+ if (bIsEncrypted) {
+ size_t len;
+ BYTE *pResult = m_crypto->encodeString(dbcwWork.value.pszVal, &len);
+ if (pResult != NULL) {
+ pEncoded = dbcwWork.value.pbVal = pResult;
+ dbcwWork.value.cpbVal = (WORD)len;
+ dbcwWork.value.type = DBVT_ENCRYPTED;
+ }
+ }
+ break;
+
+ case DBVT_UNENCRYPTED:
+ dbcwNotif.value.type = dbcwWork.value.type = DBVT_UTF8;
+ goto LBL_WriteString;
+
+ case DBVT_BLOB: case DBVT_ENCRYPTED:
+ if (dbcwWork.value.pbVal == NULL)
+ return 1;
+ break;
+ default:
+ return 1;
+ }
+
+ mir_cslockfull lck(m_csDbAccess);
+
+ char *szCachedSettingName = m_cache->GetCachedSetting(dbcwWork.szModule, dbcwWork.szSetting, moduleNameLen, settingNameLen);
+
+ // we don't cache blobs and passwords
+ if (dbcwWork.value.type != DBVT_BLOB && dbcwWork.value.type != DBVT_ENCRYPTED && !bIsEncrypted) {
+ DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1);
+ if (pCachedValue != NULL) {
+ bool bIsIdentical = false;
+ if (pCachedValue->type == dbcwWork.value.type) {
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: bIsIdentical = pCachedValue->bVal == dbcwWork.value.bVal; break;
+ case DBVT_WORD: bIsIdentical = pCachedValue->wVal == dbcwWork.value.wVal; break;
+ case DBVT_DWORD: bIsIdentical = pCachedValue->dVal == dbcwWork.value.dVal; break;
+ case DBVT_UTF8:
+ case DBVT_ASCIIZ: bIsIdentical = strcmp(pCachedValue->pszVal, dbcwWork.value.pszVal) == 0; break;
+ }
+ if (bIsIdentical)
+ return 0;
+ }
+ m_cache->SetCachedVariant(&dbcwWork.value, pCachedValue);
+ }
+ if (szCachedSettingName[-1] != 0) {
+ lck.unlock();
+ NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwWork);
+ return 0;
+ }
+ }
+ else m_cache->GetCachedValuePtr(contactID, szCachedSettingName, -1);
+
+ DBSettingSortingKey keySearch;
+ keySearch.dwContactID = contactID;
+ keySearch.dwOfsModule = GetModuleNameOfs(dbcws->szModule);
+ strncpy_s(keySearch.szSettingName, dbcws->szSetting, _TRUNCATE);
+
+ ham_key_t key = { 2 * sizeof(DWORD) + settingNameLen, &keySearch };
+ ham_record_t rec = { 0 };
+
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: rec.size = 2; break;
+ case DBVT_WORD: rec.size = 3; break;
+ case DBVT_DWORD: rec.size = 5; break;
+
+ case DBVT_ASCIIZ:
+ case DBVT_UTF8:
+ rec.size = 3 + dbcwWork.value.cchVal; break;
+
+ case DBVT_BLOB:
+ case DBVT_ENCRYPTED:
+ rec.size = 3 + dbcwWork.value.cpbVal; break;
+
+ default:
+ return 1;
+ }
+
+ rec.data = _alloca(rec.size);
+ BYTE *pBlob = (BYTE*)rec.data;
+ *pBlob++ = dbcwWork.value.type;
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: *pBlob = dbcwWork.value.bVal; break;
+ case DBVT_WORD: *(WORD*)pBlob = dbcwWork.value.wVal; break;
+ case DBVT_DWORD: *(DWORD*)pBlob = dbcwWork.value.dVal; break;
+
+ case DBVT_ASCIIZ:
+ case DBVT_UTF8:
+ *(WORD*)pBlob = dbcwWork.value.cchVal;
+ memcpy(pBlob+2, dbcwWork.value.pszVal, dbcwWork.value.cchVal);
+ break;
+
+ case DBVT_BLOB:
+ case DBVT_ENCRYPTED:
+ *(WORD*)pBlob = dbcwWork.value.cpbVal;
+ memcpy(pBlob+2, dbcwWork.value.pbVal, dbcwWork.value.cpbVal);
+ }
+ ham_db_insert(m_dbSettings, NULL, &key, &rec, HAM_OVERWRITE);
+ lck.unlock();
+
+ // notify
+ NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwNotif);
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!szModule || !szSetting)
+ return 1;
+
+ // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name
+ int settingNameLen = (int)strlen(szSetting);
+ int moduleNameLen = (int)strlen(szModule);
+ if (settingNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("DeleteContactSetting() got a > 255 setting name length. \n");
+#endif
+ return 1;
+ }
+ if (moduleNameLen > 0xFE) {
+#ifdef _DEBUG
+ OutputDebugStringA("DeleteContactSetting() got a > 255 module name length. \n");
+#endif
+ return 1;
+ }
+
+ MCONTACT saveContact = contactID;
+ {
+ mir_cslock lck(m_csDbAccess);
+ char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen);
+ if (szCachedSettingName[-1] == 0) { // it's not a resident variable
+ DBSettingSortingKey keySearch;
+ keySearch.dwContactID = contactID;
+ keySearch.dwOfsModule = GetModuleNameOfs(szModule);
+ strncpy_s(keySearch.szSettingName, szSetting, _TRUNCATE);
+
+ ham_key_t key = { 2 * sizeof(DWORD) + settingNameLen, &keySearch };
+ if (ham_db_erase(m_dbSettings, NULL, &key, 0) != HAM_SUCCESS)
+ return 1;
+ }
+
+ m_cache->GetCachedValuePtr(saveContact, szCachedSettingName, -1);
+ }
+
+ // notify
+ DBCONTACTWRITESETTING dbcws = { 0 };
+ dbcws.szModule = szModule;
+ dbcws.szSetting = szSetting;
+ dbcws.value.type = DBVT_DELETED;
+ NotifyEventHooks(hSettingChangeEvent, saveContact, (LPARAM)&dbcws);
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::EnumContactSettings(MCONTACT contactID, DBCONTACTENUMSETTINGS* dbces)
+{
+ if (!dbces->szModule)
+ return -1;
+
+ mir_cslock lck(m_csDbAccess);
+
+ DBSettingSortingKey keySearch;
+ keySearch.dwContactID = contactID;
+ keySearch.dwOfsModule = GetModuleNameOfs(dbces->szModule);
+ memset(keySearch.szSettingName, 0, SIZEOF(keySearch.szSettingName));
+
+ ham_record_t rec = { 0 };
+ ham_key_t key = { sizeof(keySearch), &keySearch };
+
+ cursor_ptr cursor(m_dbSettings);
+ if (ham_cursor_find(cursor, &key, &rec, HAM_FIND_GEQ_MATCH) != HAM_SUCCESS)
+ return -1;
+
+ int result = 0;
+ do {
+ DBSettingSortingKey *pKey = (DBSettingSortingKey*)key.data;
+ if (pKey->dwContactID != contactID || pKey->dwOfsModule != keySearch.dwOfsModule)
+ break;
+
+ char szSetting[256];
+ strncpy_s(szSetting, pKey->szSettingName, key.size - sizeof(DWORD) * 2);
+ result = (dbces->pfnEnumProc)(szSetting, dbces->lParam);
+ } while (ham_cursor_move(cursor, &key, &rec, HAM_CURSOR_NEXT) == HAM_SUCCESS);
+
+ return result;
+}
+
+STDMETHODIMP_(BOOL) CDbxKV::EnumResidentSettings(DBMODULEENUMPROC pFunc, void *pParam)
+{
+ for (int i = 0; i < m_lResidentSettings.getCount(); i++) {
+ int ret = pFunc(m_lResidentSettings[i], 0, (LPARAM)pParam);
+ if (ret) return ret;
+ }
+ return 0;
+}
diff --git a/plugins/Dbx_kv/src/hamsterdb/AUTHORS b/plugins/Dbx_kv/src/hamsterdb/AUTHORS new file mode 100644 index 0000000000..6b0d2a235f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/AUTHORS @@ -0,0 +1,6 @@ + +Ger Hobbelt (http://www.hobbelt.com, http://www.hebbut.net - THANKS!) + ham_env_get_parameters, ham_db_get_parameters and functions for approximate + matching, minor bugfixes and performance improvements plus documentation + fixes/improvements; a complete rewrite of the freelist code with HUGE + performance gains - THANKS! diff --git a/plugins/Dbx_kv/src/hamsterdb/COPYING b/plugins/Dbx_kv/src/hamsterdb/COPYING new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/Dbx_kv/src/hamsterdb/CREDITS b/plugins/Dbx_kv/src/hamsterdb/CREDITS new file mode 100644 index 0000000000..d2571aeebd --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/CREDITS @@ -0,0 +1,6 @@ + +Jul 20, 2009 +ham_env_get_parameters, ham_db_get_parameters and functions for approximate +matching, minor bugfixes and performance improvements plus documentation +improvements were written by Ger Hobbelt, http://www.hobbelt.com, +http://www.hebbut.net - THANKS! diff --git a/plugins/Dbx_kv/src/hamsterdb/NEWS b/plugins/Dbx_kv/src/hamsterdb/NEWS new file mode 100644 index 0000000000..da7acb2eb3 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/NEWS @@ -0,0 +1 @@ +See http://hamsterdb.com for up-to-date news about the project. diff --git a/plugins/Dbx_kv/src/hamsterdb/README b/plugins/Dbx_kv/src/hamsterdb/README new file mode 100644 index 0000000000..66fba73f22 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/README @@ -0,0 +1,261 @@ +hamsterdb 2.1.10 Mo 23. Feb 23:07:52 CET 2015 +(C) Christoph Rupp, chris@crupp.de; http://www.hamsterdb.com + +This is the README file of hamsterdb. + +Contents: + +1. About + +hamsterdb is a database engine written in C/C++. It is fast, production-proven +and easy to use. + +This release has several bug fixes (see below for a list). Thanks to those who +reported them and invested lots of time to come up with samples to reproduce +the bugs. + +The flag HAM_RECORD_NUMBER is deprecated. It is replaced with +HAM_RECORD_NUMBER64 for 64bit record numbers, and HAM_RECORD_NUMBER32 for +32bit record numbers. + +A major change under the hood: dirty pages are now flushed asynchronously. +Expect performance improvements in this release, and more to come in the +next releases. + +2. Changes + +New Features +* Added Cursor.TryFind to hamsterdb-dotnet + (thanks, mjmckp <matthew.j.m.peacock@gmail.com>) +* The page cache eviction was moved to a background thread +* When reading records from mmapped storage, a pointer into the storage + is returned and the record data is no longer copied + +Bugfixes +* Fixed FreeBSD compilation errors (thanks, Heping Wen) +* issue #46: fixed segfault in approx. matching (thanks, Joel + Jacobson) +* issue #45: fixed segfault in Journal recovery (thanks, Michael + Moellney) +* issue #44: approx. matching returned the wrong key (thanks, Joel + Jacobson) +* issue #43: fixed segfault when flushing transactions (thanks, Joel + Jacobson) +* Fixed compilation error on debian Wheezy, gcc 4.7.2, 32bit (thanks, + Thomas Fähnle) +* Fixed compilation error on OSX (thanks, Daniel Lemire) +* issue #42: ham_cursor_find returned wrong key w/ approx. matching and + transactions +* Fixed large file support on linux (thanks, Thomas Fähnle) + +Other Changes +* Default compilation flag is now -O3 +* Added a new parameter HAM_PARAM_POSIX_FADVISE (thanks, Thomas Fähnle) +* Removed dependency to malloc.h +* The github wiki is now linked into documentation/wiki +* The macro HAM_API_REVISION is now deprecated; use HAM_VERSION_* instead +* Deprecated HAM_RECORD_NUMBER (use HAM_RECORD_NUMBER64 instead); + introduced a new flag HAM_RECORD_NUMBER32 for 32bit record numbers +* Implemented ham_cursor_get_record_size() for remote access + +To see a list of all changes, look in the file ChangeLog. + +3. Roadmap +- See https://github.com/cruppstahl/hamsterdb/wiki/Roadmap + +4. Features + +- PRO: SIMD instructions for lookups +- PRO: transparent AES encryption +- PRO: transparent CRC32 verification +- PRO: transparent compression for journal, keys and records using + zlib, snappy, lzf or lzo +- PRO: compression for uint32 keys + +- Very fast sorted B+Tree with variable length keys +- Basic schema support for POD types (i.e. uint32, uint64, real32 etc) +- Very fast analytical functions +- Can run as an in-memory database +- Multiple databases in one file +- Record number databases ("auto-increment") +- Duplicate keys +- Logging and recovery +- Unlimited number of parallel Transactions +- Partial reading/writing of records +- Network access (remote databases) via TCP/Protocol Buffers +- Very fast database cursors +- Configurable page size, cache size, key size etc +- Runs on Linux, Unices, Microsoft Windows and other architectures +- Uses memory mapped I/O for fast disk access (but falls back to read/write if + mmap is not available) +- Uses 64bit file pointers and supports huge files (>2 GB) +- Easy to use and well-documented +- Open source and released under APL 2.0 license +- Wrappers for C++, Java, .NET, Erlang, Python, Ada and others + +5. Known Issues/Bugs + +None. + +6. Compiling + +6.1 Linux, MacOS and other Unix systems + +To compile hamsterdb, run ./configure, make, make install. + +Run `./configure --help' for more options (i.e. static/dynamic library, +build with debugging symbols etc). + +6.2 Microsoft Visual Studio 8 + +A Solution file is provided for Microsoft Visual C++ in the "win32" folder +for MSVC 2008 and MSVC 2010. +All libraries can be downloaded precompiled from the hamsterdb webpage. + +To download Microsoft Visual Studio Express Edition for free, go to +http://msdn.microsoft.com/vstudio/express/visualc/default.aspx. + +6.3 Dependencies + +On Ubuntu, the following packages are required: + - libdb-dev (optional) + - protobuf-compiler + - libprotobuf-dev + - libgoogle-perftools-dev + - libboost-system-dev + - libboost-thread-dev + - libboost-dev + - (libuv needs to be installed from sources - see + https://github.com/joyent/libuv) + +For Windows, precompiled dependencies are available here: +https://github.com/cruppstahl/hamsterdb-alien + +7. Testing and Example Code + +Make automatically compiles several example programs in the directory +'samples'. To see hamsterdb in action, just run 'samples/db1' +or any other sample. (or 'win32/out/samples/db1/db1.exe' on Windows platforms). + +8. API Documentation + +The header files in 'include/ham' have extensive comments. Also, a doxygen +script is available; run 'make doc' to start doxygen. The generated +documentation is also available on the hamsterdb web page. + +9. Other Ways to Compile hamsterdb + +If you want to compile hamsterdb without using the provided ./configure +environment, you have to set some preprocessor macros: + +DEBUG enable debugging output and diagnostic checks (slow!) +HAM_32BIT compile for 32bit (alias: WIN32) +HAM_64BIT compile for 64bit (alias: WIN64, also needs WIN32) + +Also, if you compile for windows, you have to compile the file +'src/os_win32.cc' and ignore the file 'src/os_posix.cc'. Vice versa on +non-Windows platforms. + +10. Porting hamsterdb + +Porting hamsterdb shouldn't be too difficult. All operating +system dependend functions are declared in 'src/os.h' and defined +in 'src/os_win32.cc' or 'src/os_posix.cc'. +Other compiler- and OS-specific macros are in 'include/ham/types.h'. +Most likely, these are the only files which have to be touched. Also see item +9) for important macros. + +11. Migrating files from older versions + +Usually, hamsterdb releases are backwards compatible. There are some exceptions, +though. In this case tools are provided to migrate the database. First, export +your existing database with ham_export linked against the old version. +(ham_export links statically and will NOT be confused if your system has a +newer version of hamsterdb installed). Then use the newest version of +ham_import to import the data into a new database. You can find ham_export +and ham_import in the "tools" subdirectory. + + Example (ham_export of 2.1.2 was renamed to ham_export-2.1.2 to document + that it's an older version): + + ham_export-2.1.2 input.db | ham_import --stdin output.db + +12. Licensing + +hamsterdb is released under the APL 2.0 license, which allows +unrestricted use for commercial and non-commercial applications. See the +file COPYING for more information. + +A commercial, closed source version hamsterdb pro with additional functionality +is available on request. See http://hamsterdb.com for more information. + +13. Contact + +Author of hamsterdb is + Christoph Rupp + Paul-Preuss-Str. 63 + 80995 Muenchen/Germany + email: chris@crupp.de + web: http://www.hamsterdb.com + +14. Other Copyrights + +The Google Protocol Buffers ("protobuf") library is Copyright 2008, Google Inc. +It has the following license: + + Copyright 2008, Google Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS 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 COPYRIGHT + OWNER OR CONTRIBUTORS 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. + + Code generated by the Protocol Buffer compiler is owned by the owner + of the input file used when generating it. This code is not + standalone and requires a support library to be linked with it. This + support library is itself covered by the above license. + +The libuv library is part of the Node project: http://nodejs.org/ +libuv may be distributed alone under Node's license: + + Copyright Joyent, Inc. and other Node contributors. All rights reserved. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + diff --git a/plugins/Dbx_kv/src/hamsterdb/config.h b/plugins/Dbx_kv/src/hamsterdb/config.h new file mode 100644 index 0000000000..d1fbc4d2f5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/config.h @@ -0,0 +1,10 @@ +#define _CRT_SECURE_NO_WARNINGS + +#define HAM_EXPORT + +#define BOOST_SYSTEM_NO_DEPRECATED + +#define HAVE_MMAP 1 +#define HAVE_UNMMAP 1 + +#pragma warning(disable:4100 4127 4512)
\ No newline at end of file diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.h new file mode 100644 index 0000000000..668cfc7cde --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.h @@ -0,0 +1,2535 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file hamsterdb.h + * @brief Include file for hamsterdb Embedded Storage + * @author Christoph Rupp, chris@crupp.de + * @version 2.1.10 + * + * @mainpage + * + * This manual documents the hamsterdb C API. hamsterdb is a key/value database + * that is linked directly into your application, avoiding all the overhead + * that is related to external databases and RDBMS systems. + * + * This header file declares all functions and macros that are needed to use + * hamsterdb. The comments are formatted in Doxygen style and can be extracted + * to automagically generate documentation. The documentation is also available + * online here: <a href="http://hamsterdb.com/public/scripts/html_www"> + http://hamsterdb.com/public/scripts/html_www</a>. + * + * In addition, there's a tutorial book hosted on github: + * <a href="http://github.com/cruppstahl/hamsterdb/wiki/Tutorial"> + http://github.com/cruppstahl/hamsterdb/wiki/Tutorial</a>. + * + * If you want to create or open Databases or Environments (a collection of + * multiple Databases), the following functions will be interesting for you: + * <table> + * <tr><td>@ref ham_env_create</td><td>Creates an Environment</td></tr> + * <tr><td>@ref ham_env_open</td><td>Opens an Environment</td></tr> + * <tr><td>@ref ham_env_close</td><td>Closes an Environment</td></tr> + * <tr><td>@ref ham_env_create_db</td><td>Creates a Database in an + Environment</td></tr> + * <tr><td>@ref ham_env_open_db</td><td>Opens a Database from an + Environment</td></tr> + * <tr><td>@ref ham_db_close</td><td>Closes a Database</td></tr> + * </table> + * + * To insert, lookup or delete key/value pairs, the following functions are + * used: + * <table> + * <tr><td>@ref ham_db_insert</td><td>Inserts a key/value pair into a + Database</td></tr> + * <tr><td>@ref ham_db_find</td><td>Lookup of a key/value pair in a + Database</td></tr> + * <tr><td>@ref ham_db_erase</td><td>Erases a key/value pair from a + Database</td></tr> + * </table> + * + * Alternatively, you can use Cursors to iterate over a Database: + * <table> + * <tr><td>@ref ham_cursor_create</td><td>Creates a new Cursor</td></tr> + * <tr><td>@ref ham_cursor_find</td><td>Positions the Cursor on a key</td></tr> + * <tr><td>@ref ham_cursor_insert</td><td>Inserts a new key/value pair with a + Cursor</td></tr> + * <tr><td>@ref ham_cursor_erase</td><td>Deletes the key/value pair that + the Cursor points to</td></tr> + * <tr><td>@ref ham_cursor_overwrite</td><td>Overwrites the value of the current key</td></tr> + * <tr><td>@ref ham_cursor_move</td><td>Moves the Cursor to the first, next, + previous or last key in the Database</td></tr> + * <tr><td>@ref ham_cursor_close</td><td>Closes the Cursor</td></tr> + * </table> + * + * If you want to use Transactions, then the following functions are required: + * <table> + * <tr><td>@ref ham_txn_begin</td><td>Begins a new Transaction</td></tr> + * <tr><td>@ref ham_txn_commit</td><td>Commits the current + Transaction</td></tr> + * <tr><td>@ref ham_txn_abort</td><td>Aborts the current Transaction</td></tr> + * </table> + * + * hamsterdb supports remote Databases. The server can be embedded + * into your application or run standalone (see tools/hamzilla for a Unix + * daemon or Win32 service which hosts Databases). If you want to embed the + * server then the following functions have to be used: + * <table> + * <tr><td>@ref ham_srv_init</td><td>Initializes the server</td></tr> + * <tr><td>@ref ham_srv_add_env</td><td>Adds an Environment to the + server. The Environment with all its Databases will then be available + remotely.</td></tr> + * <tr><td>@ref ham_srv_close</td><td>Closes the server and frees all allocated + resources</td></tr> + * </table> + * + * If you need help then you're always welcome to use the <a + href="https://groups.google.com/forum/?fromgroups#!forum/hamsterdb-user"> + mailing list</a>, + * drop a message (chris at crupp dot de) or use the <a + href="http://hamsterdb.com/index/contact">contact form</a>. + * + * Have fun! + */ + +#ifndef HAM_HAMSTERDB_H +#define HAM_HAMSTERDB_H + +#include <ham/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* deprecated */ +#define HAM_API_REVISION 3 + +/** + * The version numbers + * + * @remark A change of the major revision means a significant update + * with a lot of new features and API changes. + * + * The minor version means a significant update without API changes, and the + * revision is incremented for each release with minor improvements only. + * + * The file version describes the version of the binary database format. + * hamsterdb is neither backwards- nor forwards-compatible regarding file + * format changes. + * + * If a file was created with hamsterdb pro then the msb of the file version + * is set. hamsterdb pro is able to open files created with hamsterdb (APL + * version), but not vice versa. + * + * History of file versions: + * 2.1.0: introduced the file version; version is 0 + * 2.1.3: new btree format, file format cleanups; version is 1 + * 2.1.4: new btree format for duplicate keys/var. length keys; version is 2 + * 2.1.5: new freelist; version is 3 + * 2.1.10: changes in btree node format; version is 4 + */ +#define HAM_VERSION_MAJ 2 +#define HAM_VERSION_MIN 1 +#define HAM_VERSION_REV 10 +#define HAM_FILE_VERSION 4 + +/** + * The hamsterdb Database structure + * + * This structure is allocated in @ref ham_env_create_db and + * @ref ham_env_open_db. It is deleted in @a ham_db_close. + */ +struct ham_db_t; +typedef struct ham_db_t ham_db_t; + +/** + * The hamsterdb Environment structure + * + * This structure is allocated with @ref ham_env_create and @ref ham_env_open + * and is deleted in @ref ham_env_close. + */ +struct ham_env_t; +typedef struct ham_env_t ham_env_t; + +/** + * A Database Cursor + * + * A Cursor is used for bi-directionally traversing the Database and + * for inserting/deleting/searching Database items. + * + * This structure is allocated with @ref ham_cursor_create and deleted with + * @ref ham_cursor_close. + */ +struct ham_cursor_t; +typedef struct ham_cursor_t ham_cursor_t; + +/** + * A generic record. + * + * A record represents data items in hamsterdb. Before using a record, it + * is important to initialize all record fields with zeroes, i.e. with + * the C library routines memset(3) or bzero(2). + * + * When hamsterdb returns a record structure, the pointer to the record + * data is provided in @a data. This pointer is only temporary and will be + * overwritten by subsequent hamsterdb API calls using the same Transaction + * (or, if Transactions are disabled, using the same Database). The pointer + * will also be invalidated after the Transaction is aborted or committed. + * + * To avoid this, the calling application can allocate the @a data pointer. + * In this case, you have to set the flag @ref HAM_RECORD_USER_ALLOC. The + * @a size parameter will then return the size of the record. It's the + * responsibility of the caller to make sure that the @a data parameter is + * large enough for the record. + * + * The record->data pointer is not threadsafe. For threadsafe access it is + * recommended to use @a HAM_RECORD_USER_ALLOC or have each thread manage its + * own Transaction. + */ +typedef struct { + /** The size of the record data, in bytes */ + uint32_t size; + + /** Pointer to the record data */ + void *data; + + /** The record flags; see @ref HAM_RECORD_USER_ALLOC */ + uint32_t flags; + + /** Offset for partial reading/writing; see @ref HAM_PARTIAL */ + uint32_t partial_offset; + + /** Size for partial reading/writing; see @ref HAM_PARTIAL */ + uint32_t partial_size; + +} ham_record_t; + +/** Flag for @ref ham_record_t (only really useful in combination with + * @ref ham_cursor_move, @ref ham_cursor_find and @ref ham_db_find) + */ +#define HAM_RECORD_USER_ALLOC 1 + +/** + * A macro to statically initialize a @ref ham_record_t structure. + * + * Usage: + * ham_record_t rec = ham_make_record(ptr, size); + */ +#define ham_make_record(PTR, SIZE) { SIZE, PTR, 0 } + +/** + * A generic key. + * + * A key represents key items in hamsterdb. Before using a key, it + * is important to initialize all key fields with zeroes, i.e. with + * the C library routines memset(3) or bzero(2). + * + * hamsterdb usually uses keys to insert, delete or search for items. + * However, when using Database Cursors and the function @ref ham_cursor_move, + * hamsterdb also returns keys. In this case, the pointer to the key + * data is provided in @a data. This pointer is only temporary and will be + * overwritten by subsequent calls to @ref ham_cursor_move using the + * same Transaction (or, if Transactions are disabled, using the same Database). + * The pointer will also be invalidated after the Transaction is aborted + * or committed. + * + * To avoid this, the calling application can allocate the @a data pointer. + * In this case, you have to set the flag @ref HAM_KEY_USER_ALLOC. The + * @a size parameter will then return the size of the key. It's the + * responsibility of the caller to make sure that the @a data parameter is + * large enough for the key. + * + * The key->data pointer is not threadsafe. For threadsafe access it is + * recommended to use @a HAM_KEY_USER_ALLOC or have each thread manage its + * own Transaction. + */ +typedef struct { + /** The size of the key, in bytes */ + uint16_t size; + + /** The data of the key */ + void *data; + + /** The key flags; see @ref HAM_KEY_USER_ALLOC */ + uint32_t flags; + + /** For internal use */ + uint32_t _flags; + +} ham_key_t; + +/** + * A macro to statically initialize a @ref ham_key_t structure. + * + * Usage: + * ham_key_t key = ham_make_key(ptr, size); + */ +#define ham_make_key(PTR, SIZE) { SIZE, PTR, 0 } + +/** Flag for @ref ham_key_t (only really useful in combination with + * @ref ham_cursor_move, @ref ham_cursor_find and @ref ham_db_find) + */ +#define HAM_KEY_USER_ALLOC 1 + +/** + * A named parameter. + * + * These parameter structures are used for functions like @ref ham_env_open, + * @ref ham_env_create, etc. to pass variable length parameter lists. + * + * The lists are always arrays of type ham_parameter_t, with a terminating + * element of { 0, NULL}, e.g. + * + * <pre> + * ham_parameter_t parameters[] = { + * { HAM_PARAM_CACHE_SIZE, 2 * 1024 * 1024 }, // set cache size to 2 mb + * { HAM_PARAM_PAGE_SIZE, 4096 }, // set page size to 4 kb + * { 0, NULL } + * }; + * </pre> + */ +typedef struct { + /** The name of the parameter; all HAM_PARAM_*-constants */ + uint32_t name; + + /** The value of the parameter. */ + uint64_t value; + +} ham_parameter_t; + + +/** + * @defgroup ham_key_types hamsterdb Key Types + * @{ + */ + +/** A binary blob without type; sorted by memcmp */ +#define HAM_TYPE_BINARY 0 +/** A binary blob without type; sorted by callback function */ +#define HAM_TYPE_CUSTOM 1 +/** An unsigned 8-bit integer */ +#define HAM_TYPE_UINT8 3 +/** An unsigned 16-bit integer */ +#define HAM_TYPE_UINT16 5 +/** An unsigned 32-bit integer */ +#define HAM_TYPE_UINT32 7 +/** An unsigned 64-bit integer */ +#define HAM_TYPE_UINT64 9 +/** An 32-bit float */ +#define HAM_TYPE_REAL32 11 +/** An 64-bit double */ +#define HAM_TYPE_REAL64 12 + +/** + * @} + */ + + +/** + * @defgroup ham_status_codes hamsterdb Status Codes + * @{ + */ + +/** Operation completed successfully */ +#define HAM_SUCCESS ( 0) +/** Invalid record size */ +#define HAM_INV_RECORD_SIZE ( -2) +/** Invalid key size */ +#define HAM_INV_KEY_SIZE ( -3) +/* deprecated */ +#define HAM_INV_KEYSIZE HAM_INV_KEY_SIZE +/** Invalid page size (must be 1024 or a multiple of 2048) */ +#define HAM_INV_PAGE_SIZE ( -4) +/* deprecated */ +#define HAM_INV_PAGESIZE HAM_INV_PAGE_SIZE +/** Memory allocation failed - out of memory */ +#define HAM_OUT_OF_MEMORY ( -6) +/** Invalid function parameter */ +#define HAM_INV_PARAMETER ( -8) +/** Invalid file header */ +#define HAM_INV_FILE_HEADER ( -9) +/** Invalid file version */ +#define HAM_INV_FILE_VERSION (-10) +/** Key was not found */ +#define HAM_KEY_NOT_FOUND (-11) +/** Tried to insert a key which already exists */ +#define HAM_DUPLICATE_KEY (-12) +/** Internal Database integrity violated */ +#define HAM_INTEGRITY_VIOLATED (-13) +/** Internal hamsterdb error */ +#define HAM_INTERNAL_ERROR (-14) +/** Tried to modify the Database, but the file was opened as read-only */ +#define HAM_WRITE_PROTECTED (-15) +/** Database record not found */ +#define HAM_BLOB_NOT_FOUND (-16) +/** Generic file I/O error */ +#define HAM_IO_ERROR (-18) +/** Function is not yet implemented */ +#define HAM_NOT_IMPLEMENTED (-20) +/** File not found */ +#define HAM_FILE_NOT_FOUND (-21) +/** Operation would block */ +#define HAM_WOULD_BLOCK (-22) +/** Object was not initialized correctly */ +#define HAM_NOT_READY (-23) +/** Database limits reached */ +#define HAM_LIMITS_REACHED (-24) +/** Object was already initialized */ +#define HAM_ALREADY_INITIALIZED (-27) +/** Database needs recovery */ +#define HAM_NEED_RECOVERY (-28) +/** Cursor must be closed prior to Transaction abort/commit */ +#define HAM_CURSOR_STILL_OPEN (-29) +/** Record filter or file filter not found */ +#define HAM_FILTER_NOT_FOUND (-30) +/** Operation conflicts with another Transaction */ +#define HAM_TXN_CONFLICT (-31) +/* internal use: key was erased in a Transaction */ +#define HAM_KEY_ERASED_IN_TXN (-32) +/** Database cannot be closed because it is modified in a Transaction */ +#define HAM_TXN_STILL_OPEN (-33) +/** Cursor does not point to a valid item */ +#define HAM_CURSOR_IS_NIL (-100) +/** Database not found */ +#define HAM_DATABASE_NOT_FOUND (-200) +/** Database name already exists */ +#define HAM_DATABASE_ALREADY_EXISTS (-201) +/** Database already open, or: Database handle is already initialized */ +#define HAM_DATABASE_ALREADY_OPEN (-202) +/** Environment already open, or: Environment handle is already initialized */ +#define HAM_ENVIRONMENT_ALREADY_OPEN (-203) +/** Invalid log file header */ +#define HAM_LOG_INV_FILE_HEADER (-300) +/** Remote I/O error/Network error */ +#define HAM_NETWORK_ERROR (-400) + +/** + * @} + */ + + +/** + * @defgroup ham_static hamsterdb Static Functions + * @{ + */ + +/** + * A typedef for a custom error handler function + * + * This error handler can be used in combination with + * @ref ham_set_errhandler(). + * + * @param message The error message + * @param level The error level: + * <ul> + * <li>@ref HAM_DEBUG_LEVEL_DEBUG (0) </li> a debug message + * <li>@ref HAM_DEBUG_LEVEL_NORMAL (1) </li> a normal error message + * <li>2</li> reserved + * <li>@ref HAM_DEBUG_LEVEL_FATAL (3) </li> a fatal error message + * </ul> + * + * @sa error_levels + */ +typedef void HAM_CALLCONV (*ham_errhandler_fun)(int level, const char *message); + +/** A debug message */ +#define HAM_DEBUG_LEVEL_DEBUG 0 + +/** A normal error message */ +#define HAM_DEBUG_LEVEL_NORMAL 1 + +/** A fatal error message */ +#define HAM_DEBUG_LEVEL_FATAL 3 + +/** + * Sets the global error handler + * + * This handler will receive all debug messages that are emitted + * by hamsterdb. You can install the default handler by setting @a f to 0. + * + * The default error handler prints all messages to stderr. To install a + * different logging facility, you can provide your own error handler. + * + * Note that the callback function must have the same calling convention + * as the hamsterdb library. + * + * @param f A pointer to the error handler function, or NULL to restore + * the default handler + */ +HAM_EXPORT void HAM_CALLCONV +ham_set_errhandler(ham_errhandler_fun f); + +/** + * Translates a hamsterdb status code to a descriptive error string + * + * @param status The hamsterdb status code + * + * @return A pointer to a descriptive error string + */ +HAM_EXPORT const char * HAM_CALLCONV +ham_strerror(ham_status_t status); + +/** + * Returns the version of the hamsterdb library + * + * @param major If not NULL, will return the major version number + * @param minor If not NULL, will return the minor version number + * @param revision If not NULL, will return the revision version number + */ +HAM_EXPORT void HAM_CALLCONV +ham_get_version(uint32_t *major, uint32_t *minor, + uint32_t *revision); + +/** + * @} + */ + + +/** + * @defgroup ham_env hamsterdb Environment Functions + * @{ + */ + +/** + * Creates a Database Environment + * + * A Database Environment is a collection of Databases, which are all stored + * in one physical file (or in-memory). The maximum number of Databases + * depends on the page size; the default is above 600. + * + * Each Database in an Environment is identified by a positive 16bit + * value (except 0 and values at or above 0xf000). + * Databases in an Environment can be created with @ref ham_env_create_db + * or opened with @ref ham_env_open_db. + * + * Specify a URL instead of a filename (i.e. + * "ham://localhost:8080/customers.db") to access a remote hamsterdb Server. + * + * To enable ACID Transactions, supply the flag @ref HAM_ENABLE_TRANSACTIONS. + * By default, hamsterdb will use a Journal for recovering the Environment + * and its data in case of a crash, and also to re-apply committed Transactions + * which were not yet flushed to disk. This Journalling can be disabled + * with the flag @ref HAM_DISABLE_RECOVERY. (It is disabled if the Environment + * is in-memory.) + * + * If Transactions are not required, but hamsterdb should still be able to + * recover in case of a crash or power outage, then the flag + * @ref HAM_ENABLE_RECOVERY will enable the Journal (without allowing + * Transactions.) + * + * For performance reasons the Journal does not use fsync(2) (or + * FlushFileBuffers on Win32) to flush modified buffers to disk. Use the flag + * @ref HAM_ENABLE_FSYNC to force the use of fsync. + * + * @param env A pointer to an Environment handle + * @param filename The filename of the Environment file. If the file already + * exists, it is overwritten. Can be NULL for an In-Memory + * Environment. Can be a URL ("ham://<hostname>:<port>/<environment>") + * for remote access. + * @param flags Optional flags for opening the Environment, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_ENABLE_FSYNC</li> Flushes all file handles after + * committing or aborting a Transaction using fsync(), fdatasync() + * or FlushFileBuffers(). This file has no effect + * if Transactions are disabled. Slows down performance but makes + * sure that all file handles and operating system caches are + * transferred to disk, thus providing a stronger durability. + * <li>@ref HAM_IN_MEMORY</li> Creates an In-Memory Environment. No + * file will be created, and the Database contents are lost after + * the Environment is closed. The @a filename parameter can + * be NULL. Do <b>NOT</b> specify @a cache_size other than 0. + * <li>@ref HAM_DISABLE_MMAP</li> Do not use memory mapped files for I/O. + * By default, hamsterdb checks if it can use mmap, + * since mmap is faster than read/write. For performance + * reasons, this flag should not be used. + * <li>@ref HAM_CACHE_UNLIMITED</li> Do not limit the cache. Nearly as + * fast as an In-Memory Database. Not allowed in combination + * with a limited cache size. + * <li>@ref HAM_ENABLE_TRANSACTIONS</li> Enables Transactions for this + * Environment. This flag implies @ref HAM_ENABLE_RECOVERY. + * <li>@ref HAM_ENABLE_RECOVERY</li> Enables logging/recovery for this + * Environment. Not allowed in combination with @ref HAM_IN_MEMORY. + * <li>@ref HAM_DISABLE_RECOVERY</li> Disables logging/recovery for this + * Environment. + * <li>@ref HAM_FLUSH_WHEN_COMMITTED</li> Immediately flushes committed + * Transactions and writes them to the Btree. Disabled by default. If + * disabled then hamsterdb buffers committed Transactions and only starts + * flushing when too many Transactions were committed. + * </ul> + * + * @param mode File access rights for the new file. This is the @a mode + * parameter for creat(2). Ignored on Microsoft Windows. Default + * is 0644. + * @param param An array of ham_parameter_t structures. The following + * parameters are available: + * <ul> + * <li>@ref HAM_PARAM_CACHE_SIZE</li> The size of the Database cache, + * in bytes. The default size is defined in src/config.h + * as @a HAM_DEFAULT_CACHE_SIZE - usually 2MB + * <li>@ref HAM_PARAM_POSIX_FADVISE</li> Sets the "advice" for + * posix_fadvise(). Only on supported platforms. Allowed values are + * @ref HAM_POSIX_FADVICE_NORMAL (which is the default) or + * @ref HAM_POSIX_FADVICE_RANDOM. + * <li>@ref HAM_PARAM_PAGE_SIZE</li> The size of a file page, in + * bytes. It is recommended not to change the default size. The + * default size depends on hardware and operating system. + * Page sizes must be 1024 or a multiple of 2048. + * <li>@ref HAM_PARAM_FILE_SIZE_LIMIT</li> Sets a file size limit (in bytes). + * Disabled by default. Not allowed in combination with @ref HAM_IN_MEMORY. + * If the limit is exceeded, API functions return @ref HAM_LIMITS_REACHED. + * <li>@ref HAM_PARAM_LOG_DIRECTORY</li> The path of the log file + * and the journal files; default is the same path as the database + * file. Ignored for remote Environments. + * <li>@ref HAM_PARAM_NETWORK_TIMEOUT_SEC</li> Timeout (in seconds) when + * waiting for data from a remote server. By default, no timeout is set. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or an + * invalid combination of flags or parameters was specified + * @return @ref HAM_IO_ERROR if the file could not be opened or + * reading/writing failed + * @return @ref HAM_INV_FILE_VERSION if the Environment version is not + * compatible with the library version + * @return @ref HAM_OUT_OF_MEMORY if memory could not be allocated + * @return @ref HAM_INV_PAGE_SIZE if @a page_size is not 1024 or + * a multiple of 2048 + * @return @ref HAM_INV_KEY_SIZE if @a key_size is too large (at least 4 + * keys must fit in a page) + * @return @ref HAM_WOULD_BLOCK if another process has locked the file + * @return @ref HAM_ENVIRONMENT_ALREADY_OPEN if @a env is already in use + * + * @sa ham_env_create + * @sa ham_env_close + * @sa ham_env_open + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_create(ham_env_t **env, const char *filename, + uint32_t flags, uint32_t mode, const ham_parameter_t *param); + +/** + * Opens an existing Database Environment + * + * This function opens an existing Database Environment. + * + * A Database Environment is a collection of Databases, which are all stored + * in one physical file (or in-memory). + * + * Each Database in an Environment is identified by a positive 16bit + * value (except 0 and values at or above 0xf000). + * Databases in an Environment can be created with @ref ham_env_create_db + * or opened with @ref ham_env_open_db. + * + * Specify a URL instead of a filename (i.e. + * "ham://localhost:8080/customers.db") to access a remote hamsterdb Server. + * + * Also see the documentation @ref ham_env_create about Transactions, Recovery + * and the use of fsync. + * + * @param env A valid Environment handle + * @param filename The filename of the Environment file, or URL of a hamsterdb + * Server + * @param flags Optional flags for opening the Environment, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_READ_ONLY </li> Opens the file for reading only. + * Operations that need write access (i.e. @ref ham_db_insert) will + * return @ref HAM_WRITE_PROTECTED. + * <li>@ref HAM_ENABLE_FSYNC</li> Flushes all file handles after + * committing or aborting a Transaction using fsync(), fdatasync() + * or FlushFileBuffers(). This file has no effect + * if Transactions are disabled. Slows down performance but makes + * sure that all file handles and operating system caches are + * transferred to disk, thus providing a stronger durability. + * <li>@ref HAM_DISABLE_MMAP </li> Do not use memory mapped files for I/O. + * By default, hamsterdb checks if it can use mmap, + * since mmap is faster than read/write. For performance + * reasons, this flag should not be used. + * <li>@ref HAM_CACHE_UNLIMITED </li> Do not limit the cache. Nearly as + * fast as an In-Memory Database. Not allowed in combination + * with a limited cache size. + * <li>@ref HAM_ENABLE_TRANSACTIONS </li> Enables Transactions for this + * Environment. This flag imples @ref HAM_ENABLE_RECOVERY. + * <li>@ref HAM_ENABLE_RECOVERY </li> Enables logging/recovery for this + * Environment. Will return @ref HAM_NEED_RECOVERY, if the Environment + * is in an inconsistent state. Not allowed in combination + * with @ref HAM_IN_MEMORY. + * <li>@ref HAM_DISABLE_RECOVERY</li> Disables logging/recovery for this + * Environment. + * <li>@ref HAM_AUTO_RECOVERY </li> Automatically recover the Environment, + * if necessary. This flag implies @ref HAM_ENABLE_RECOVERY. + * <li>@ref HAM_FLUSH_WHEN_COMMITTED</li> Immediately flushes committed + * Transactions and writes them to the Btree. Disabled by default. If + * disabled then hamsterdb buffers committed Transactions and only starts + * flushing when too many Transactions were committed. + * </ul> + * @param param An array of ham_parameter_t structures. The following + * parameters are available: + * <ul> + * <li>@ref HAM_PARAM_CACHE_SIZE </li> The size of the Database cache, + * in bytes. The default size is defined in src/config.h + * as @a HAM_DEFAULT_CACHE_SIZE - usually 2MB + * <li>@ref HAM_PARAM_POSIX_FADVISE</li> Sets the "advice" for + * posix_fadvise(). Only on supported platforms. Allowed values are + * @ref HAM_POSIX_FADVICE_NORMAL (which is the default) or + * @ref HAM_POSIX_FADVICE_RANDOM. + * <li>@ref HAM_PARAM_FILE_SIZE_LIMIT</li> Sets a file size limit (in bytes). + * Disabled by default. If the limit is exceeded, API functions + * return @ref HAM_LIMITS_REACHED. + * <li>@ref HAM_PARAM_LOG_DIRECTORY</li> The path of the log file + * and the journal files; default is the same path as the database + * file. Ignored for remote Environments. + * <li>@ref HAM_PARAM_NETWORK_TIMEOUT_SEC</li> Timeout (in seconds) when + * waiting for data from a remote server. By default, no timeout is set. + * </ul> + * + * @return @ref HAM_SUCCESS upon success. + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL, an + * invalid combination of flags was specified + * @return @ref HAM_FILE_NOT_FOUND if the file does not exist + * @return @ref HAM_IO_ERROR if the file could not be opened or reading failed + * @return @ref HAM_INV_FILE_VERSION if the Environment version is not + * compatible with the library version. + * @return @ref HAM_OUT_OF_MEMORY if memory could not be allocated + * @return @ref HAM_WOULD_BLOCK if another process has locked the file + * @return @ref HAM_NEED_RECOVERY if the Database is in an inconsistent state + * @return @ref HAM_LOG_INV_FILE_HEADER if the logfile is corrupt + * @return @ref HAM_ENVIRONMENT_ALREADY_OPEN if @a env is already in use + * @return @ref HAM_NETWORK_ERROR if a remote server is not reachable + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_open(ham_env_t **env, const char *filename, + uint32_t flags, const ham_parameter_t *param); + +/** + * Retrieve the current value for a given Environment setting + * + * Only those values requested by the parameter array will be stored. + * + * The following parameters are supported: + * <ul> + * <li>HAM_PARAM_CACHE_SIZE</li> returns the cache size + * <li>HAM_PARAM_PAGE_SIZE</li> returns the page size + * <li>HAM_PARAM_MAX_DATABASES</li> returns the max. number of + * Databases of this Database's Environment + * <li>HAM_PARAM_FLAGS</li> returns the flags which were used to + * open or create this Database + * <li>HAM_PARAM_FILEMODE</li> returns the @a mode parameter which + * was specified when creating this Database + * <li>HAM_PARAM_FILENAME</li> returns the filename (the @a value + * of this parameter is a const char * pointer casted to a + * uint64_t variable) + * <li>@ref HAM_PARAM_LOG_DIRECTORY</li> The path of the log file + * and the journal files. Ignored for remote Environments. + * <li>@ref HAM_PARAM_JOURNAL_COMPRESSION</li> Returns the + * selected algorithm for journal compression, or 0 if compression + * is disabled + * </ul> + * + * @param env A valid Environment handle + * @param param An array of ham_parameter_t structures + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or + * @a param is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_get_parameters(ham_env_t *env, ham_parameter_t *param); + +/** + * Creates a new Database in a Database Environment + * + * An Environment can contain a (limited) amount of Databases; the exact + * limit depends on the page size and is above 600. + * + * Each Database in an Environment is identified by a positive 16bit + * value. 0 and values at or above 0xf000 are reserved. + * + * This function initializes the ham_db_t handle (the second parameter). + * When the handle is no longer in use, it should be closed with + * @ref ham_db_close. Alternatively, the Database handle is closed + * automatically if @ref ham_env_close is called with the flag + * @ref HAM_AUTO_CLEANUP. + * + * A Database can (and should) be configured and optimized for the data that + * is inserted. The data is described through flags and parameters. hamsterdb + * differentiates between several data characteristics, and offers predefined + * "types" to describe the keys. In general, the default key type + * (@ref HAM_TYPE_BINARY) is slower than the other types, and + * fixed-length binary keys (@ref HAM_TYPE_BINARY in combination with + * @ref HAM_PARAM_KEY_SIZE) is faster than variable-length binary + * keys. It is therefore recommended to always set the key size and record size, + * although it is not required. + * + * Internally, hamsterdb uses two different layouts ("default" and "pax) + * depending on the settings specified by the user. The "default" layout + * is enabled for variable-length keys or if duplicate keys are enabled. + * For fixed-length keys (without duplicates) the "pax" layout is chosen. + * The "pax" layout is more compact and usually faster. + * + * A word of warning regarding the use of fixed length binary keys + * (@ref HAM_TYPE_CUSTOM or @ref HAM_TYPE_BINARY in combination with + * @ref HAM_PARAM_KEY_SIZE): if your key size is too large, only few keys + * will fit in a Btree node. The Btree fanout will be very high, which will + * decrease performance. In such cases it might be better to NOT specify + * the key size; then hamsterdb will store keys as blobs if they are too large. + * + * See the Wiki documentation for <a href= + "https://github.com/cruppstahl/hamsterdb/wiki/Evaluating-and-Benchmarking"> + * Evaluating and Benchmarking</a> on how to test different configurations and + * optimize for performance. + * + * The key type is set with @ref HAM_PARAM_KEY_TYPE and can have either + * of the following values: + * + * <ul> + * <li>HAM_TYPE_BINARY</li> This is the default key type: a binary blob. + * Internally, hamsterdb uses memcmp(3) for the sort order. Key size depends + * on @ref HAM_PARAM_KEY_SIZE and is unlimited (@ref HAM_KEY_SIZE_UNLIMITED) + * by default. + * <li>HAM_TYPE_CUSTOM</li> Similar to @ref HAM_TYPE_BINARY, but + * uses a callback function for the sort order. This function is supplied + * by the application with @sa ham_db_set_compare_func. + * <li>HAM_TYPE_UINT8</li> Key is a 8bit (1 byte) unsigned integer + * <li>HAM_TYPE_UINT16</li> Key is a 16bit (2 byte) unsigned integer + * <li>HAM_TYPE_UINT32</li> Key is a 32bit (4 byte) unsigned integer + * <li>HAM_TYPE_UINT64</li> Key is a 64bit (8 byte) unsigned integer + * <li>HAM_TYPE_REAL32</li> Key is a 32bit (4 byte) float + * <li>HAM_TYPE_REAL64</li> Key is a 64bit (8 byte) double + * </ul> + * + * If the key type is ommitted then @ref HAM_TYPE_BINARY is the default. + * + * If binary/custom keys are so big that they cannot be stored in the Btree, + * then the full key will be stored in an overflow area, which has + * performance implications when accessing such keys. + * + * In addition to the flags above, you can specify @a HAM_ENABLE_DUPLICATE_KEYS + * to insert duplicate keys, i.e. to model 1:n or n:m relationships. + * + * If the size of the records is always constant, then + * @ref HAM_PARAM_RECORD_SIZE should be used to specify this size. This allows + * hamsterdb to optimize the record storage, and small records will + * automatically be stored in the Btree's leaf nodes instead of a separately + * allocated blob, allowing faster access. + * A record size of 0 is valid and suited for boolean values ("key exists" + * vs "key doesn't exist"). The default record size is + * @ref HAM_RECORD_SIZE_UNLIMITED. + * + * @param env A valid Environment handle. + * @param db A valid Database handle, which will point to the created + * Database. To close the handle, use @ref ham_db_close. + * @param name The name of the Database. If a Database with this name + * already exists, the function will fail with + * @ref HAM_DATABASE_ALREADY_EXISTS. Database names from 0xf000 to + * 0xffff and 0 are reserved. + * @param flags Optional flags for creating the Database, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_ENABLE_DUPLICATE_KEYS </li> Enable duplicate keys for this + * Database. By default, duplicate keys are disabled. + * <li>@ref HAM_RECORD_NUMBER32 </li> Creates an "auto-increment" Database. + * Keys in Record Number Databases are automatically assigned an + * incrementing 32bit value. If key->data is not NULL + * (and key->flags is @ref HAM_KEY_USER_ALLOC), the value of the current + * key is returned in @a key. If key-data is NULL and key->size is 0, + * key->data is temporarily allocated by hamsterdb. + * <li>@ref HAM_RECORD_NUMBER64 </li> Creates an "auto-increment" Database. + * Keys in Record Number Databases are automatically assigned an + * incrementing 64bit value. If key->data is not NULL + * (and key->flags is @ref HAM_KEY_USER_ALLOC), the value of the current + * key is returned in @a key. If key-data is NULL and key->size is 0, + * key->data is temporarily allocated by hamsterdb. + * </ul> + * + * @param params An array of ham_parameter_t structures. The following + * parameters are available: + * <ul> + * <li>@ref HAM_PARAM_KEY_TYPE </li> The type of the keys in the B+Tree + * index. The default is @ref HAM_TYPE_BINARY. See above for more + * information. + * <li>@ref HAM_PARAM_KEY_SIZE </li> The (fixed) size of the keys in + * the B+Tree index; or @ref HAM_KEY_SIZE_UNLIMITED for unlimited and + * variable keys (this is the default). + * <li>@ref HAM_PARAM_RECORD_SIZE </li> The (fixed) size of the records; + * or @ref HAM_RECORD_SIZE_UNLIMITED if there was no fixed record size + * specified (this is the default). + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or an + * invalid combination of flags was specified + * @return @ref HAM_DATABASE_ALREADY_EXISTS if a Database with this @a name + * already exists in this Environment + * @return @ref HAM_OUT_OF_MEMORY if memory could not be allocated + * @return @ref HAM_LIMITS_REACHED if the maximum number of Databases per + * Environment was already created + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_create_db(ham_env_t *env, ham_db_t **db, + uint16_t name, uint32_t flags, const ham_parameter_t *params); + +/** + * Opens a Database in a Database Environment + * + * Each Database in an Environment is identified by a positive 16bit + * value (except 0 and values at or above 0xf000). + * + * This function initializes the ham_db_t handle (the second parameter). + * When the handle is no longer in use, it should be closed with + * @ref ham_db_close. Alternatively, the Database handle is closed + * automatically if @ref ham_env_close is called with the flag + * @ref HAM_AUTO_CLEANUP. + * + * @param env A valid Environment handle + * @param db A valid Database handle, which will point to the opened + * Database. To close the handle, use @see ham_db_close. + * @param name The name of the Database. If a Database with this name + * does not exist, the function will fail with + * @ref HAM_DATABASE_NOT_FOUND. + * @param flags Optional flags for opening the Database, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_READ_ONLY </li> Opens the Database for reading only. + * Operations that need write access (i.e. @ref ham_db_insert) will + * return @ref HAM_WRITE_PROTECTED. + * </ul> + * @param params Reserved; set to NULL + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or an + * invalid combination of flags was specified + * @return @ref HAM_DATABASE_NOT_FOUND if a Database with this @a name + * does not exist in this Environment. + * @return @ref HAM_DATABASE_ALREADY_OPEN if this Database was already + * opened + * @return @ref HAM_OUT_OF_MEMORY if memory could not be allocated + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_open_db(ham_env_t *env, ham_db_t **db, + uint16_t name, uint32_t flags, const ham_parameter_t *params); + +/** + * Renames a Database in an Environment. + * + * @param env A valid Environment handle. + * @param oldname The old name of the existing Database. If a Database + * with this name does not exist, the function will fail with + * @ref HAM_DATABASE_NOT_FOUND. + * @param newname The new name of this Database. If a Database + * with this name already exists, the function will fail with + * @ref HAM_DATABASE_ALREADY_EXISTS. + * @param flags Optional flags for renaming the Database, combined with + * bitwise OR; unused, set to 0. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or if + * the new Database name is reserved + * @return @ref HAM_DATABASE_NOT_FOUND if a Database with this @a name + * does not exist in this Environment + * @return @ref HAM_DATABASE_ALREADY_EXISTS if a Database with the new name + * already exists + * @return @ref HAM_OUT_OF_MEMORY if memory could not be allocated + * @return @ref HAM_NOT_READY if the Environment @a env was not initialized + * correctly (i.e. not yet opened or created) + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_rename_db(ham_env_t *env, uint16_t oldname, + uint16_t newname, uint32_t flags); + +/** + * Deletes a Database from an Environment + * + * @param env A valid Environment handle + * @param name The name of the Database to delete. If a Database + * with this name does not exist, the function will fail with + * @ref HAM_DATABASE_NOT_FOUND. If the Database was already opened, + * the function will fail with @ref HAM_DATABASE_ALREADY_OPEN. + * @param flags Optional flags for deleting the Database; unused, set to 0. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a env pointer is NULL or if + * the new Database name is reserved + * @return @ref HAM_DATABASE_NOT_FOUND if a Database with this @a name + * does not exist + * @return @ref HAM_DATABASE_ALREADY_OPEN if a Database with this name is + * still open + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_erase_db(ham_env_t *env, uint16_t name, uint32_t flags); + +/* internal flag - only flush committed transactions, not the btree pages */ +#define HAM_FLUSH_COMMITTED_TRANSACTIONS 1 + +/** + * Flushes the Environment + * + * This function flushes the Environment caches and writes the whole file + * to disk. All Databases of this Environment are flushed as well. + * + * Since In-Memory Databases do not have a file on disk, the + * function will have no effect and will return @ref HAM_SUCCESS. + * + * @param env A valid Environment handle + * @param flags Optional flags for flushing; unused, set to 0 + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_flush(ham_env_t *env, uint32_t flags); + +/* internal use only - don't lock mutex */ +#define HAM_DONT_LOCK 0xf0000000 + +/** + * Returns the names of all Databases in an Environment + * + * This function returns the names of all Databases and the number of + * Databases in an Environment. + * + * The memory for @a names must be allocated by the user. @a count + * must be the size of @a names when calling the function, and will be + * the number of Databases when the function returns. The function returns + * @ref HAM_LIMITS_REACHED if @a names is not big enough; in this case, the + * caller should resize the array and call the function again. + * + * @param env A valid Environment handle + * @param names Pointer to an array for the Database names + * @param count Pointer to the size of the array; will be used to store the + * number of Databases when the function returns. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a env, @a names or @a count is NULL + * @return @ref HAM_LIMITS_REACHED if @a names is not large enough to hold + * all Database names + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_get_database_names(ham_env_t *env, uint16_t *names, + uint32_t *count); + +/** + * Closes the Database Environment + * + * This function closes the Database Environment. It also frees the + * memory resources allocated in the @a env handle, and tries to truncate + * the file (see below). + * + * If the flag @ref HAM_AUTO_CLEANUP is specified, hamsterdb automatically + * calls @ref ham_db_close with flag @ref HAM_AUTO_CLEANUP on all open + * Databases (which closes all open Databases and their Cursors). This + * invalidates the ham_db_t and ham_cursor_t handles! + * + * If the flag is not specified, the application must close all Database + * handles with @ref ham_db_close to prevent memory leaks. + * + * This function also aborts all Transactions which were not yet committed, + * and therefore renders all Transaction handles invalid. If the flag + * @ref HAM_TXN_AUTO_COMMIT is specified, all Transactions will be committed. + * + * This function also tries to truncate the file and "cut off" unused space + * at the end of the file to reduce the file size. This feature is disabled + * on Win32 if memory mapped I/O is used (see @ref HAM_DISABLE_MMAP). + * + * @param env A valid Environment handle + * @param flags Optional flags for closing the handle. Possible flags are: + * <ul> + * <li>@ref HAM_AUTO_CLEANUP. Calls @ref ham_db_close with the flag + * @ref HAM_AUTO_CLEANUP on every open Database + * <li>@ref HAM_TXN_AUTO_COMMIT. Automatically commit all open + * Transactions + * <li>@ref HAM_TXN_AUTO_ABORT. Automatically abort all open + * Transactions; this is the default behaviour + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a env is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_close(ham_env_t *env, uint32_t flags); + +/** + * @} + */ + + +/** + * @defgroup ham_txn hamsterdb Transaction Functions + * @{ + */ + +/** + * The hamsterdb Transaction structure + * + * This structure is allocated with @ref ham_txn_begin and deleted with + * @ref ham_txn_commit or @ref ham_txn_abort. + */ +struct ham_txn_t; +typedef struct ham_txn_t ham_txn_t; + +/** + * Begins a new Transaction + * + * A Transaction is an atomic sequence of Database operations. With @ref + * ham_txn_begin such a new sequence is started. To write all operations of this + * sequence to the Database use @ref ham_txn_commit. To abort and cancel + * this sequence use @ref ham_txn_abort. + * + * In order to use Transactions, the Environment has to be created or + * opened with the flag @ref HAM_ENABLE_TRANSACTIONS. + * + * You can create as many Transactions as you want (older versions of + * hamsterdb did not allow to create more than one Transaction in parallel). + * + * @param txn Pointer to a pointer of a Transaction structure + * @param env A valid Environment handle + * @param name An optional Transaction name + * @param reserved A reserved pointer; always set to NULL + * @param flags Optional flags for beginning the Transaction, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_TXN_READ_ONLY </li> This Transaction is read-only and + * will not modify the Database. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_OUT_OF_MEMORY if memory allocation failed + */ +HAM_EXPORT ham_status_t +ham_txn_begin(ham_txn_t **txn, ham_env_t *env, const char *name, + void *reserved, uint32_t flags); + +/** Flag for @ref ham_txn_begin */ +#define HAM_TXN_READ_ONLY 1 + +/* Internal flag for @ref ham_txn_begin */ +#define HAM_TXN_TEMPORARY 2 + +/** + * Retrieves the Transaction name + * + * @returns NULL if the name was not assigned or if @a txn is invalid + */ +HAM_EXPORT const char * +ham_txn_get_name(ham_txn_t *txn); + +/** + * Commits a Transaction + * + * This function applies the sequence of Database operations. + * + * Note that the function will fail with @ref HAM_CURSOR_STILL_OPEN if + * a Cursor was attached to this Transaction (with @ref ham_cursor_create + * or @ref ham_cursor_clone), and the Cursor was not closed. + * + * @param txn Pointer to a Transaction structure + * @param flags Optional flags for committing the Transaction, combined with + * bitwise OR. Unused, set to 0. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_IO_ERROR if writing to the file failed + * @return @ref HAM_CURSOR_STILL_OPEN if there are Cursors attached to this + * Transaction + */ +HAM_EXPORT ham_status_t +ham_txn_commit(ham_txn_t *txn, uint32_t flags); + +/** + * Aborts a Transaction + * + * This function aborts (= cancels) the sequence of Database operations. + * + * Note that the function will fail with @ref HAM_CURSOR_STILL_OPEN if + * a Cursor was attached to this Transaction (with @ref ham_cursor_create + * or @ref ham_cursor_clone), and the Cursor was not closed. + * + * @param txn Pointer to a Transaction structure + * @param flags Optional flags for aborting the Transaction, combined with + * bitwise OR. Unused, set to 0. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_IO_ERROR if writing to the Database file or logfile failed + * @return @ref HAM_CURSOR_STILL_OPEN if there are Cursors attached to this + * Transaction + */ +HAM_EXPORT ham_status_t +ham_txn_abort(ham_txn_t *txn, uint32_t flags); + +/** + * @} + */ + + +/** + * @defgroup ham_database hamsterdb Database Functions + * @{ + */ + +/** Flag for @ref ham_env_open, @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_ENABLE_FSYNC 0x00000001 + +/* unused 0x00000002 */ + +/** Flag for @ref ham_env_open, @ref ham_env_open_db. + * This flag is non persistent. */ +#define HAM_READ_ONLY 0x00000004 + +/* unused 0x00000008 */ + +/* unused 0x00000010 */ + +/* reserved 0x00000020 */ + +/* unused 0x00000040 */ + +/** Flag for @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_IN_MEMORY 0x00000080 + +/* reserved: DB_USE_MMAP (not persistent) 0x00000100 */ + +/** Flag for @ref ham_env_open, @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_DISABLE_MMAP 0x00000200 + +/* deprecated */ +#define HAM_RECORD_NUMBER HAM_RECORD_NUMBER64 + +/** Flag for @ref ham_env_create_db. + * This flag is persisted in the Database. */ +#define HAM_RECORD_NUMBER32 0x00001000 + +/** Flag for @ref ham_env_create_db. + * This flag is persisted in the Database. */ +#define HAM_RECORD_NUMBER64 0x00002000 + +/** Flag for @ref ham_env_create_db. + * This flag is persisted in the Database. */ +#define HAM_ENABLE_DUPLICATE_KEYS 0x00004000 +/* deprecated */ +#define HAM_ENABLE_DUPLICATES HAM_ENABLE_DUPLICATE_KEYS + +/** Flag for @ref ham_env_create, @ref ham_env_open. + * This flag is non persistent. */ +#define HAM_ENABLE_RECOVERY 0x00008000 + +/** Flag for @ref ham_env_open. + * This flag is non persistent. */ +#define HAM_AUTO_RECOVERY 0x00010000 + +/** Flag for @ref ham_env_create, @ref ham_env_open. + * This flag is non persistent. */ +#define HAM_ENABLE_TRANSACTIONS 0x00020000 + +/** Flag for @ref ham_env_open, @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_CACHE_UNLIMITED 0x00040000 + +/** Flag for @ref ham_env_create, @ref ham_env_open. + * This flag is non persistent. */ +#define HAM_DISABLE_RECOVERY 0x00080000 + +/* internal use only! (not persistent) */ +#define HAM_IS_REMOTE_INTERNAL 0x00200000 + +/* internal use only! (not persistent) */ +#define HAM_DISABLE_RECLAIM_INTERNAL 0x00400000 + +/* internal use only! (persistent) */ +#define HAM_FORCE_RECORDS_INLINE 0x00800000 + +/** Flag for @ref ham_env_open, @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_FLUSH_WHEN_COMMITTED 0x01000000 + +/** Pro: Flag for @ref ham_env_open, @ref ham_env_create. + * This flag is non persistent. */ +#define HAM_ENABLE_CRC32 0x02000000 + +/** + * Returns the last error code + * + * @note This API is deprecated! It will be removed in one of the + * next versions. + * + * @param db A valid Database handle + * + * @return The last error code which was returned by one of the + * hamsterdb API functions. Use @ref ham_strerror to translate + * this code to a descriptive string + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_get_error(ham_db_t *db); + +/** + * Typedef for a key comparison function + * + * @remark This function compares two index keys. It returns -1, if @a lhs + * ("left-hand side", the parameter on the left side) is smaller than + * @a rhs ("right-hand side"), 0 if both keys are equal, and 1 if @a lhs + * is larger than @a rhs. + */ +typedef int HAM_CALLCONV (*ham_compare_func_t)(ham_db_t *db, + const uint8_t *lhs, uint32_t lhs_length, + const uint8_t *rhs, uint32_t rhs_length); + +/** + * Sets the comparison function + * + * The comparison function compares two index keys. It returns -1 if the + * first key is smaller, +1 if the second key is smaller or 0 if both + * keys are equal. + * + * Supplying a comparison function is only allowed for the key type + * @ref HAM_TYPE_CUSTOM; see the documentation of @sa ham_env_create_db + * for more information. + * + * @param db A valid Database handle + * @param foo A pointer to the compare function + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + * @return @ref HAM_INV_PARAMETER if the database's key type was not + * specified as @ref HAM_TYPE_CUSTOM + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_set_compare_func(ham_db_t *db, ham_compare_func_t foo); + +/** + * Searches an item in the Database + * + * This function searches the Database for @a key. If the key + * is found, @a record will receive the record of this item and + * @ref HAM_SUCCESS is returned. If the key is not found, the function + * returns @ref HAM_KEY_NOT_FOUND. + * + * A ham_record_t structure should be initialized with + * zeroes before it is being used. This can be done with the C library + * routines memset(3) or bzero(2). + * + * If the function completes successfully, the @a record pointer is + * initialized with the size of the record (in @a record.size) and the + * actual record data (in @a record.data). If the record is empty, + * @a size is 0 and @a data points to NULL. + * + * The @a data pointer is a temporary pointer and will be overwritten + * by subsequent hamsterdb API calls using the same Transaction + * (or, if Transactions are disabled, using the same Database). + * You can alter this behaviour by allocating the @a data pointer in + * the application and setting @a record.flags to @ref HAM_RECORD_USER_ALLOC. + * Make sure that the allocated buffer is large enough. + * + * When specifying @ref HAM_DIRECT_ACCESS, the @a data pointer will point + * directly to the record that is stored in hamsterdb; the data can be modified, + * but the pointer must not be reallocated or freed. The flag @ref + * HAM_DIRECT_ACCESS is only allowed in In-Memory Databases and not if + * Transactions are enabled. + * + * @ref ham_db_find can not search for duplicate keys. If @a key has + * multiple duplicates, only the first duplicate is returned. + * + * You can read only portions of the record by specifying the flag + * @ref HAM_PARTIAL. In this case, hamsterdb will read + * <b>record->partial_size</b> bytes of the record data at offset + * <b>record->partial_offset</b>. If necessary, the record data will + * be limited to the original record size. The number of actually read + * bytes is returned in <b>record->partial_size</b>. The original size of + * the record is stored in <b>record->size</b>. + * + * @ref HAM_PARTIAL is not allowed if record->size is <= 8 or if Transactions + * are enabled. In such a case, @ref HAM_INV_PARAMETER is returned. + * + * If Transactions are enabled (see @ref HAM_ENABLE_TRANSACTIONS) and + * @a txn is NULL then hamsterdb will create a temporary Transaction. + * When moving the Cursor, and the new key is currently modified in an + * active Transaction (one that is not yet committed or aborted) then + * hamsterdb will skip this key and move to the next/previous one. However if + * @a flags are 0 (and the Cursor is not moved), and @a key or @a rec + * is NOT NULL, then hamsterdb will return error @ref HAM_TXN_CONFLICT. + * + * @param db A valid Database handle + * @param txn A Transaction handle, or NULL + * @param key The key of the item + * @param record The record of the item + * @param flags Optional flags for searching, which can be combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_FIND_LT_MATCH </li> Cursor 'find' flag 'Less Than': the + * cursor is moved to point at the last record which' key + * is less than the specified key. When such a record cannot + * be located, an error is returned. + * <li>@ref HAM_FIND_GT_MATCH </li> Cursor 'find' flag 'Greater Than': + * the cursor is moved to point at the first record which' key is + * larger than the specified key. When such a record cannot be + * located, an error is returned. + * <li>@ref HAM_FIND_LEQ_MATCH </li> Cursor 'find' flag 'Less or EQual': + * the cursor is moved to point at the record which' key matches + * the specified key and when such a record is not available + * the cursor is moved to point at the last record which' key + * is less than the specified key. When such a record cannot be + * located, an error is returned. + * <li>@ref HAM_FIND_GEQ_MATCH </li> Cursor 'find' flag 'Greater or + * Equal': the cursor is moved to point at the record which' key + * matches the specified key and when such a record + * is not available the cursor is moved to point at the first + * record which' key is larger than the specified key. + * When such a record cannot be located, an error is returned. + * <li>@ref HAM_FIND_NEAR_MATCH </li> Cursor 'find' flag 'Any Near Or + * Equal': the cursor is moved to point at the record which' + * key matches the specified key and when such a record is + * not available the cursor is moved to point at either the + * last record which' key is less than the specified key or + * the first record which' key is larger than the specified + * key, whichever of these records is located first. + * When such records cannot be located, an error is returned. + * <li>@ref HAM_DIRECT_ACCESS </li> Only for In-Memory Databases + * and not if Transactions are enabled! + * Returns a direct pointer to the data blob stored by the + * hamsterdb engine. This pointer must not be resized or freed, + * but the data in this memory can be modified. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db, @a key or @a record is NULL + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS is specified, + * but the Database is not an In-Memory Database. + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS and + * @a HAM_ENABLE_TRANSACTIONS were both specified. + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is set but record + * size is <= 8 or Transactions are enabled + * @return @ref HAM_KEY_NOT_FOUND if the @a key does not exist + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + * + * @remark When either or both @ref HAM_FIND_LT_MATCH and/or @ref + * HAM_FIND_GT_MATCH have been specified as flags, the @a key structure + * will be overwritten when an approximate match was found: the + * @a key and @a record structures will then point at the located + * @a key and @a record. In this case the caller should ensure @a key + * points at a structure which must adhere to the same restrictions + * and conditions as specified for @ref ham_cursor_move(..., + * HAM_CURSOR_NEXT). + * + * @sa HAM_RECORD_USER_ALLOC + * @sa HAM_KEY_USER_ALLOC + * @sa ham_record_t + * @sa ham_key_t + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_find(ham_db_t *db, ham_txn_t *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags); + +/** + * Inserts a Database item + * + * This function inserts a key/record pair as a new Database item. + * + * If the key already exists in the Database, error @ref HAM_DUPLICATE_KEY + * is returned. + * + * If you wish to overwrite an existing entry specify the + * flag @ref HAM_OVERWRITE. + * + * You can write only portions of the record by specifying the flag + * @ref HAM_PARTIAL. In this case, hamsterdb will write <b>partial_size</b> + * bytes of the record data at offset <b>partial_offset</b>. The full record + * size will always be given in <b>record->size</b>! If + * partial_size+partial_offset exceed record->size then partial_size will + * be limited. To shrink or grow the record, adjust record->size. + * @ref HAM_PARTIAL automatically overwrites existing records. + * Gaps will be filled with null-bytes if the record did not yet exist. + * + * @ref HAM_PARTIAL is not allowed if record->size is <= 8 or if Transactions + * are enabled. In such a case, @ref HAM_INV_PARAMETER is returned. + * + * If you wish to insert a duplicate key specify the flag @ref HAM_DUPLICATE. + * (Note that the Database has to be created with @ref HAM_ENABLE_DUPLICATE_KEYS + * in order to use duplicate keys.) + * The duplicate key is inserted after all other duplicate keys (see + * @ref HAM_DUPLICATE_INSERT_LAST). + * + * Record Number Databases (created with @ref HAM_RECORD_NUMBER32 or + * @ref HAM_RECORD_NUMBER64) expect either an empty @a key (with a size of + * 0 and data pointing to NULL), or a user-supplied key (with key.flag + * @ref HAM_KEY_USER_ALLOC and a valid data pointer). + * If key.size is 0 and key.data is NULL, hamsterdb will temporarily + * allocate memory for key->data, which will then point to an 4-byte (or 8-byte) + * unsigned integer. + * + * For very fast sequential inserts please use @ref ham_cursor_insert in + * combination with the flag @ref HAM_HINT_APPEND. + * + * @param db A valid Database handle + * @param txn A Transaction handle, or NULL + * @param key The key of the new item + * @param record The record of the new item + * @param flags Optional flags for inserting. Possible flags are: + * <ul> + * <li>@ref HAM_OVERWRITE. If the @a key already exists, the record is + * overwritten. Otherwise, the key is inserted. Flag is not + * allowed in combination with @ref HAM_DUPLICATE. + * <li>@ref HAM_DUPLICATE. If the @a key already exists, a duplicate + * key is inserted. The key is inserted before the already + * existing key, or according to the sort order. Flag is not + * allowed in combination with @ref HAM_OVERWRITE. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db, @a key or @a record is NULL + * @return @ref HAM_INV_PARAMETER if the Database is a Record Number Database + * and the key is invalid (see above) + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is set but record + * size is <= 8 or Transactions are enabled + * @return @ref HAM_INV_PARAMETER if the flags @ref HAM_OVERWRITE <b>and</b> + * @ref HAM_DUPLICATE were specified, or if @ref HAM_DUPLICATE + * was specified, but the Database was not created with + * flag @ref HAM_ENABLE_DUPLICATE_KEYS. + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is specified and + * record->partial_offset+record->partial_size exceeds the + * record->size + * @return @ref HAM_WRITE_PROTECTED if you tried to insert a key in a read-only + * Database + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + * @return @ref HAM_INV_KEY_SIZE if the key size is larger than the + * @a HAM_PARAMETER_KEY_SIZE parameter specified for + * @ref ham_env_create_db + * OR if the key's size is greater than the Btree key size (see + * @ref HAM_PARAM_KEY_SIZE). + * @return @ref HAM_INV_RECORD_SIZE if the record size is different from + * the one specified with @a HAM_PARAM_RECORD_SIZE + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_insert(ham_db_t *db, ham_txn_t *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags); + +/** + * Flag for @ref ham_db_insert and @ref ham_cursor_insert + * + * When specified with @ref ham_db_insert and in case a key + * is specified which stores duplicates in the Database, the first + * duplicate record will be overwritten. + * + * When used with @ref ham_cursor_insert and assuming the same + * conditions, the duplicate currently referenced by the Cursor + * will be overwritten. +*/ +#define HAM_OVERWRITE 0x0001 + +/** Flag for @ref ham_db_insert and @ref ham_cursor_insert */ +#define HAM_DUPLICATE 0x0002 + +/** Flag for @ref ham_cursor_insert */ +#define HAM_DUPLICATE_INSERT_BEFORE 0x0004 + +/** Flag for @ref ham_cursor_insert */ +#define HAM_DUPLICATE_INSERT_AFTER 0x0008 + +/** Flag for @ref ham_cursor_insert */ +#define HAM_DUPLICATE_INSERT_FIRST 0x0010 + +/** Flag for @ref ham_cursor_insert */ +#define HAM_DUPLICATE_INSERT_LAST 0x0020 + +/** Flag for @ref ham_db_find, @ref ham_cursor_find, @ref ham_cursor_move */ +#define HAM_DIRECT_ACCESS 0x0040 + +/** Flag for @ref ham_db_insert, @ref ham_cursor_insert, @ref ham_db_find, + * @ref ham_cursor_find, @ref ham_cursor_move */ +#define HAM_PARTIAL 0x0080 + +/* Internal flag for @ref ham_db_find, @ref ham_cursor_find, + * @ref ham_cursor_move */ +#define HAM_FORCE_DEEP_COPY 0x0100 + +/** + * Flag for @ref ham_cursor_insert + * + * Mutually exclusive with flag @ref HAM_HINT_PREPEND. + * + * Hints the hamsterdb engine that the current key will + * compare as @e larger than any key already existing in the Database. + * The hamsterdb engine will verify this postulation and when found not + * to be true, will revert to a regular insert operation + * as if this flag was not specified. The incurred cost then is only one + * additional key comparison. + */ +#define HAM_HINT_APPEND 0x00080000 + +/** + * Flag for @ref ham_cursor_insert + * + * Mutually exclusive with flag @ref HAM_HINT_APPEND. + * + * Hints the hamsterdb engine that the current key will + * compare as @e smaller than any key already existing in the Database. + * The hamsterdb engine will verify this postulation and when found not + * to be true, will revert to a regular insert operation + * as if this flag was not specified. The incurred cost then is only one + * additional key comparison. + */ +#define HAM_HINT_PREPEND 0x00100000 + +/** + * Flag mask to extract the common hint flags from a find/move/insert/erase + * flag value. + */ +#define HAM_HINTS_MASK 0x001F0000 + +/** + * Erases a Database item + * + * This function erases a Database item. If the item @a key + * does not exist, @ref HAM_KEY_NOT_FOUND is returned. + * + * Note that ham_db_erase can not erase a single duplicate key. If the key + * has multiple duplicates, all duplicates of this key will be erased. Use + * @ref ham_cursor_erase to erase a specific duplicate key. + * + * @param db A valid Database handle + * @param txn A Transaction handle, or NULL + * @param key The key to delete + * @param flags Optional flags for erasing; unused, set to 0 + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db or @a key is NULL + * @return @ref HAM_WRITE_PROTECTED if you tried to erase a key from a read-only + * Database + * @return @ref HAM_KEY_NOT_FOUND if @a key was not found + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_erase(ham_db_t *db, ham_txn_t *txn, ham_key_t *key, uint32_t flags); + +/* internal flag for ham_db_erase() - do not use */ +#define HAM_ERASE_ALL_DUPLICATES 1 + +/** + * Returns the number of keys stored in the Database + * + * You can specify the @ref HAM_SKIP_DUPLICATES if you do now want + * to include any duplicates in the count. This will also speed up the + * counting. + * + * @param db A valid Database handle + * @param txn A Transaction handle, or NULL + * @param flags Optional flags: + * <ul> + * <li>@ref HAM_SKIP_DUPLICATES. Excludes any duplicates from + * the count + * </ul> + * @param keycount A reference to a variable which will receive + * the calculated key count per page + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db or @a keycount is NULL or when + * @a flags contains an invalid flag set + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_get_key_count(ham_db_t *db, ham_txn_t *txn, uint32_t flags, + uint64_t *keycount); + +/** + * Retrieve the current value for a given Database setting + * + * Only those values requested by the parameter array will be stored. + * + * The following parameters are supported: + * <ul> + * <li>HAM_PARAM_FLAGS</li> returns the flags which were used to + * open or create this Database + * <li>HAM_PARAM_DATABASE_NAME</li> returns the Database name + * <li>HAM_PARAM_KEY_TYPE</li> returns the Btree key type + * <li>HAM_PARAM_KEY_SIZE</li> returns the Btree key size + * or @ref HAM_KEY_SIZE_UNLIMITED if there was no fixed key size + * specified. + * <li>HAM_PARAM_RECORD_SIZE</li> returns the record size, + * or @ref HAM_RECORD_SIZE_UNLIMITED if there was no fixed record size + * specified. + * <li>HAM_PARAM_MAX_KEYS_PER_PAGE</li> returns the maximum number + * of keys per page. This number is precise if the key size is fixed + * and duplicates are disabled; otherwise it's an estimate. + * <li>@ref HAM_PARAM_RECORD_COMPRESSION</li> Returns the + * selected algorithm for record compression, or 0 if compression + * is disabled + * <li>@ref HAM_PARAM_KEY_COMPRESSION</li> Returns the + * selected algorithm for key compression, or 0 if compression + * is disabled + * </ul> + * + * @param db A valid Database handle + * @param param An array of ham_parameter_t structures + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if the @a db pointer is NULL or + * @a param is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_get_parameters(ham_db_t *db, ham_parameter_t *param); + +/** Parameter name for @ref ham_env_open, @ref ham_env_create; + * Journal files are switched whenever the number of new Transactions exceeds + * this threshold. */ +#define HAM_PARAM_JOURNAL_SWITCH_THRESHOLD 0x00001 + +/** Parameter name for @ref ham_env_open, @ref ham_env_create; + * sets the cache size */ +#define HAM_PARAM_CACHE_SIZE 0x00000100 +/* deprecated */ +#define HAM_PARAM_CACHESIZE HAM_PARAM_CACHE_SIZE + +/** Parameter name for @ref ham_env_create; sets the page size */ +#define HAM_PARAM_PAGE_SIZE 0x00000101 +/* deprecated */ +#define HAM_PARAM_PAGESIZE HAM_PARAM_PAGE_SIZE + +/** Parameter name for @ref ham_env_create_db; sets the key size */ +#define HAM_PARAM_KEY_SIZE 0x00000102 +/* deprecated */ +#define HAM_PARAM_KEYSIZE HAM_PARAM_KEY_SIZE + +/** Parameter name for @ref ham_env_get_parameters; retrieves the number + * of maximum Databases */ +#define HAM_PARAM_MAX_DATABASES 0x00000103 + +/** Parameter name for @ref ham_env_create_db; sets the key type */ +#define HAM_PARAM_KEY_TYPE 0x00000104 + +/** Parameter name for @ref ham_env_open, @ref ham_env_create; + * sets the path of the log files */ +#define HAM_PARAM_LOG_DIRECTORY 0x00000105 + +/** hamsterdb pro: Parameter name for @ref ham_env_open, @ref ham_env_create; + * sets the AES encryption key */ +#define HAM_PARAM_ENCRYPTION_KEY 0x00000106 + +/** Parameter name for @ref ham_env_open, @ref ham_env_create; + * sets the network timeout (in seconds) */ +#define HAM_PARAM_NETWORK_TIMEOUT_SEC 0x00000107 + +/** Parameter name for @ref ham_env_create_db; sets the key size */ +#define HAM_PARAM_RECORD_SIZE 0x00000108 + +/** Parameter name for @ref ham_env_create, @ref ham_env_open; sets a + * limit for the file size (in bytes) */ +#define HAM_PARAM_FILE_SIZE_LIMIT 0x00000109 + +/** Parameter name for @ref ham_env_create, @ref ham_env_open; sets the + * parameter for posix_fadvise() */ +#define HAM_PARAM_POSIX_FADVISE 0x00000110 + +/** Value for @ref HAM_PARAM_POSIX_FADVISE */ +#define HAM_POSIX_FADVICE_NORMAL 0 + +/** Value for @ref HAM_PARAM_POSIX_FADVISE */ +#define HAM_POSIX_FADVICE_RANDOM 1 + +/** Value for unlimited record sizes */ +#define HAM_RECORD_SIZE_UNLIMITED ((uint32_t)-1) + +/** Value for unlimited key sizes */ +#define HAM_KEY_SIZE_UNLIMITED ((uint16_t)-1) + +/** Retrieves the Database/Environment flags as were specified at the time of + * @ref ham_env_create/@ref ham_env_open invocation. */ +#define HAM_PARAM_FLAGS 0x00000200 + +/** Retrieves the filesystem file access mode as was specified at the time + * of @ref ham_env_create/@ref ham_env_open invocation. */ +#define HAM_PARAM_FILEMODE 0x00000201 + +/** + * Return a <code>const char *</code> pointer to the current + * Environment/Database file name in the @ref uint64_t value + * member, when the Database is actually stored on disc. + * + * In-memory Databases will return a NULL (0) pointer instead. + */ +#define HAM_PARAM_FILENAME 0x00000202 + +/** + * Retrieve the Database 'name' number of this @ref ham_db_t Database within + * the current @ref ham_env_t Environment. +*/ +#define HAM_PARAM_DATABASE_NAME 0x00000203 + +/** + * Retrieve the maximum number of keys per page; this number depends on the + * currently active page and key sizes. Can be an estimate if keys do not + * have constant sizes or if duplicate keys are used. + */ +#define HAM_PARAM_MAX_KEYS_PER_PAGE 0x00000204 + +/** + * hamsterdb pro: Parameter name for @ref ham_env_create, @ref ham_env_open; + * enables compression for the journal. + */ +#define HAM_PARAM_JOURNAL_COMPRESSION 0x00001000 + +/** + * hamsterdb pro: Parameter name for @ref ham_env_create_db, + * @ref ham_env_open_db; enables compression for the records of + * a Database. + */ +#define HAM_PARAM_RECORD_COMPRESSION 0x00001001 + +/** + * hamsterdb pro: Parameter name for @ref ham_env_create_db, + * @ref ham_env_open_db; enables compression for the records of + * a Database. + */ +#define HAM_PARAM_KEY_COMPRESSION 0x00001002 + +/** hamsterdb pro: helper macro for disabling compression */ +#define HAM_COMPRESSOR_NONE 0 + +/** + * hamsterdb pro: selects zlib compression + * http://www.zlib.net/ + */ +#define HAM_COMPRESSOR_ZLIB 1 + +/** + * hamsterdb pro: selects google snappy compression + * http://code.google.com/p/snappy + */ +#define HAM_COMPRESSOR_SNAPPY 2 + +/** + * hamsterdb pro: selects lzf compression + * http://oldhome.schmorp.de/marc/liblzf.html + */ +#define HAM_COMPRESSOR_LZF 3 + +/** + * hamsterdb pro: selects lzo compression + * http://www.oberhumer.com/opensource/lzo + */ +#define HAM_COMPRESSOR_LZO 4 + +/** + * Retrieves the Environment handle of a Database + * + * @param db A valid Database handle + * + * @return The Environment handle + */ +HAM_EXPORT ham_env_t *HAM_CALLCONV +ham_db_get_env(ham_db_t *db); + +/** + * Returns the kind of key match which produced this key as it was + * returned by one of the @ref ham_db_find() and @ref ham_cursor_find(). + * + * This routine assumes the key was passed back by one of the @ref ham_db_find + * and @ref ham_cursor_find functions and not used by any other hamsterdb + * functions after that. + * + * As such, this function produces an answer akin to the 'sign' of the + * specified key as it was returned by the find operation. + * + * @param key A valid key + * + * @return 1 (greater than) or -1 (less than) when the given key is an + * approximate result / zero (0) otherwise. Specifically: + * <ul> + * <li>+1 when the key is greater than the item searched for (key + * was a GT match) + * <li>-1 when the key is less than the item searched for (key was + * a LT match) + * <li>zero (0) otherwise (key was an EQ (EXACT) match) + * </ul> + */ +HAM_EXPORT int HAM_CALLCONV +ham_key_get_approximate_match_type(ham_key_t *key); + +/** + * Closes the Database + * + * This function flushes the Database and then closes the file handle. + * It also free the memory resources allocated in the @a db handle. + * + * If the flag @ref HAM_AUTO_CLEANUP is specified, hamsterdb automatically + * calls @ref ham_cursor_close on all open Cursors. This invalidates the + * ham_cursor_t handle! + * + * If the flag is not specified, the application must close all Database + * Cursors with @ref ham_cursor_close to prevent memory leaks. + * + * This function also aborts all Transactions which were not yet committed, + * and therefore renders all Transaction handles invalid. If the flag + * @ref HAM_TXN_AUTO_COMMIT is specified, all Transactions will be committed. + * + * @param db A valid Database handle + * @param flags Optional flags for closing the Database. Possible values are: + * <ul> + * <li>@ref HAM_AUTO_CLEANUP. Automatically closes all open Cursors + * <li>@ref HAM_TXN_AUTO_COMMIT. Automatically commit all open + * Transactions + * <li>@ref HAM_TXN_AUTO_ABORT. Automatically abort all open + * Transactions; this is the default behaviour + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db is NULL + * @return @ref HAM_CURSOR_STILL_OPEN if not all Cursors of this Database + * were closed, and @ref HAM_AUTO_CLEANUP was not specified + * @return @ref HAM_TXN_STILL_OPEN if this Database is modified by a + * currently active Transaction + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_close(ham_db_t *db, uint32_t flags); + +/** Flag for @ref ham_db_close, @ref ham_env_close */ +#define HAM_AUTO_CLEANUP 1 + +/** @internal (Internal) flag for @ref ham_db_close, @ref ham_env_close */ +#define HAM_DONT_CLEAR_LOG 2 + +/** Automatically abort all open Transactions (the default) */ +#define HAM_TXN_AUTO_ABORT 4 + +/** Automatically commit all open Transactions */ +#define HAM_TXN_AUTO_COMMIT 8 + +/** + * @} + */ + +/** + * @defgroup ham_cursor hamsterdb Cursor Functions + * @{ + */ + +/** + * Creates a Database Cursor + * + * Creates a new Database Cursor. Cursors can be used to + * traverse the Database from start to end or vice versa. Cursors + * can also be used to insert, delete or search Database items. + * + * A newly created Cursor does not point to any item in the Database. + * + * The application should close all Cursors of a Database before closing + * the Database. + * + * If Transactions are enabled (@ref HAM_ENABLE_TRANSACTIONS), but @a txn + * is NULL, then each Cursor operation (i.e. @ref ham_cursor_insert, + * @ref ham_cursor_find etc) will create its own, temporary Transaction + * <b>only</b> for the lifetime of this operation and not for the lifetime + * of the whole Cursor! + * + * @param db A valid Database handle + * @param txn A Transaction handle, or NULL + * @param flags Optional flags for creating the Cursor; unused, set to 0 + * @param cursor A pointer to a pointer which is allocated for the + * new Cursor handle + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a db or @a cursor is NULL + * @return @ref HAM_OUT_OF_MEMORY if the new structure could not be allocated + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_create(ham_cursor_t **cursor, ham_db_t *db, ham_txn_t *txn, + uint32_t flags); + +/** + * Clones a Database Cursor + * + * Clones an existing Cursor. The new Cursor will point to + * exactly the same item as the old Cursor. If the old Cursor did not point + * to any item, so will the new Cursor. + * + * If the old Cursor is bound to a Transaction, then the new Cursor will + * also be bound to this Transaction. + * + * @param src The existing Cursor + * @param dest A pointer to a pointer, which is allocated for the + * cloned Cursor handle + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a src or @a dest is NULL + * @return @ref HAM_OUT_OF_MEMORY if the new structure could not be allocated + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_clone(ham_cursor_t *src, ham_cursor_t **dest); + +/** + * Moves the Cursor + * + * Moves the Cursor. Use the @a flags to specify the direction. + * After the move, key and record of the item are returned, if @a key + * and/or @a record are valid pointers. + * + * If the direction is not specified, the Cursor will not move. Do not + * specify a direction if you want to fetch the key and/or record of + * the current item. + * + * When specifying @ref HAM_DIRECT_ACCESS, the @a data pointer will point + * directly to the record that is stored in hamsterdb; the data can be modified, + * but the pointer must not be reallocated or freed. The flag @ref + * HAM_DIRECT_ACCESS is only allowed in In-Memory Databases and not if + * Transactions are enabled. + * + * You can read only portions of the record by specifying the flag + * @ref HAM_PARTIAL. In this case, hamsterdb will read + * <b>record->partial_size</b> bytes of the record data at offset + * <b>record->partial_offset</b>. If necessary, the record data will + * be limited to the original record size. The number of actually read + * bytes is returned in <b>record->partial_size</b>. The original size of + * the record is stored in <b>record->size</b>. + * + * @ref HAM_PARTIAL is not allowed if record->size is <= 8 or if Transactions + * are enabled. In such a case, @ref HAM_INV_PARAMETER is returned. + * + * If Transactions are enabled (see @ref HAM_ENABLE_TRANSACTIONS), and + * the Cursor moves next or previous to a key which is currently modified + * in an active Transaction (one that is not yet committed or aborted), then + * hamsterdb will skip the modified key. (This behavior is different from i.e. + * @a ham_cursor_find, which would return the error @ref HAM_TXN_CONFLICT). + * + * If a key has duplicates and any of the duplicates is currently modified + * in another active Transaction, then ALL duplicate keys are skipped when + * moving to the next or previous key. + * + * If the first (@ref HAM_CURSOR_FIRST) or last (@ref HAM_CURSOR_LAST) key + * is requested, and the current key (or any of its duplicates) is currently + * modified in an active Transaction, then @ref HAM_TXN_CONFLICT is + * returned. + * + * If this Cursor is nil (i.e. because it was not yet used or the Cursor's + * item was erased) then the flag @a HAM_CURSOR_NEXT (or @a + * HAM_CURSOR_PREVIOUS) will be identical to @a HAM_CURSOR_FIRST (or + * @a HAM_CURSOR_LAST). + * + * @param cursor A valid Cursor handle + * @param key An optional pointer to a @ref ham_key_t structure. If this + * pointer is not NULL, the key of the new item is returned. + * Note that key->data will point to temporary data. This pointer + * will be invalidated by subsequent hamsterdb API calls. See + * @ref HAM_KEY_USER_ALLOC on how to change this behaviour. + * @param record An optional pointer to a @ref ham_record_t structure. If this + * pointer is not NULL, the record of the new item is returned. + * Note that record->data will point to temporary data. This pointer + * will be invalidated by subsequent hamsterdb API calls. See + * @ref HAM_RECORD_USER_ALLOC on how to change this behaviour. + * @param flags The flags for this operation. They are used to specify + * the direction for the "move". If you do not specify a direction, + * the Cursor will remain on the current position. + * <ul> + * <li>@ref HAM_CURSOR_FIRST </li> positions the Cursor on the first + * item in the Database + * <li>@ref HAM_CURSOR_LAST </li> positions the Cursor on the last + * item in the Database + * <li>@ref HAM_CURSOR_NEXT </li> positions the Cursor on the next + * item in the Database; if the Cursor does not point to any + * item, the function behaves as if direction was + * @ref HAM_CURSOR_FIRST. + * <li>@ref HAM_CURSOR_PREVIOUS </li> positions the Cursor on the + * previous item in the Database; if the Cursor does not point to + * any item, the function behaves as if direction was + * @ref HAM_CURSOR_LAST. + * <li>@ref HAM_SKIP_DUPLICATES </li> skips duplicate keys of the + * current key. Not allowed in combination with + * @ref HAM_ONLY_DUPLICATES. + * <li>@ref HAM_ONLY_DUPLICATES </li> only move through duplicate keys + * of the current key. Not allowed in combination with + * @ref HAM_SKIP_DUPLICATES. + * <li>@ref HAM_DIRECT_ACCESS </li> Only for In-Memory Databases and + * not if Transactions are enabled! + * Returns a direct pointer to the data blob stored by the + * hamsterdb engine. This pointer must not be resized or freed, + * but the data in this memory can be modified. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a cursor is NULL, or if an invalid + * combination of flags was specified + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is set but record + * size is <= 8 or Transactions are enabled + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item, but + * key and/or record were requested + * @return @ref HAM_KEY_NOT_FOUND if @a cursor points to the first (or last) + * item, and a move to the previous (or next) item was + * requested + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS is specified, + * but the Database is not an In-Memory Database. + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS and + * @a HAM_ENABLE_TRANSACTIONS were both specified. + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is specified and + * record->partial_offset+record->partial_size exceeds the + * record->size + * @return @ref HAM_TXN_CONFLICT if @ref HAM_CURSOR_FIRST or @ref + * HAM_CURSOR_LAST is specified but the first (or last) key or + * any of its duplicates is currently modified in an active + * Transaction + * + * @sa HAM_RECORD_USER_ALLOC + * @sa HAM_KEY_USER_ALLOC + * @sa ham_record_t + * @sa ham_key_t + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_move(ham_cursor_t *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + +/** Flag for @ref ham_cursor_move */ +#define HAM_CURSOR_FIRST 0x0001 + +/** Flag for @ref ham_cursor_move */ +#define HAM_CURSOR_LAST 0x0002 + +/** Flag for @ref ham_cursor_move */ +#define HAM_CURSOR_NEXT 0x0004 + +/** Flag for @ref ham_cursor_move */ +#define HAM_CURSOR_PREVIOUS 0x0008 + +/** Flag for @ref ham_cursor_move and @ref ham_db_get_key_count */ +#define HAM_SKIP_DUPLICATES 0x0010 + +/** Flag for @ref ham_cursor_move */ +#define HAM_ONLY_DUPLICATES 0x0020 + +/** + * Overwrites the current record + * + * This function overwrites the record of the current item. + * + * @param cursor A valid Cursor handle + * @param record A valid record structure + * @param flags Optional flags for overwriting the item; unused, set to 0 + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a cursor or @a record is NULL + * @return @ref HAM_INV_PARAMETER if @a cursor points to an item with + * duplicates and duplicate sorting is enabled + * @return @ref HAM_INV_PARAMETER if duplicate sorting is enabled + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_overwrite(ham_cursor_t *cursor, ham_record_t *record, + uint32_t flags); + +/** + * Searches with a key and points the Cursor to the key found, retrieves + * the located record + * + * Searches for an item in the Database and points the Cursor to this item. + * If the item could not be found, the Cursor is not modified. + * + * Note that @ref ham_cursor_find can not search for duplicate keys. If @a key + * has multiple duplicates, only the first duplicate is returned. + * + * When specifying @ref HAM_DIRECT_ACCESS, the @a data pointer will point + * directly to the record that is stored in hamsterdb; the data can be modified, + * but the pointer must not be reallocated or freed. The flag @ref + * HAM_DIRECT_ACCESS is only allowed in In-Memory Databases and not if + * Transactions are enabled. + * + * You can read only portions of the record by specifying the flag + * @ref HAM_PARTIAL. In this case, hamsterdb will read + * <b>record->partial_size</b> bytes of the record data at offset + * <b>record->partial_offset</b>. If necessary, the record data will + * be limited to the original record size. The number of actually read + * bytes is returned in <b>record->partial_size</b>. The original size of + * the record is stored in <b>record->size</b>. + * + * @ref HAM_PARTIAL is not allowed if record->size is <= 8 or if Transactions + * are enabled. In such a case, @ref HAM_INV_PARAMETER is returned. + * + * When either or both @ref HAM_FIND_LT_MATCH and/or @ref HAM_FIND_GT_MATCH + * have been specified as flags, the @a key structure will be overwritten + * when an approximate match was found: the @a key and @a record + * structures will then point at the located @a key (and @a record). + * In this case the caller should ensure @a key points at a structure + * which must adhere to the same restrictions and conditions as specified + * for @ref ham_cursor_move(...,HAM_CURSOR_*): + * key->data will point to temporary data upon return. This pointer + * will be invalidated by subsequent hamsterdb API calls using the same + * Transaction (or the same Database, if Transactions are disabled). See + * @ref HAM_KEY_USER_ALLOC on how to change this behaviour. + * + * Further note that the @a key structure must be non-const at all times as its + * internal flag bits may be written to. This is done for your benefit, as + * you may pass the returned @a key structure to + * @ref ham_key_get_approximate_match_type() to retrieve additional info about + * the precise nature of the returned key: the sign value produced + * by @ref ham_key_get_approximate_match_type() tells you which kind of match + * (equal, less than, greater than) occurred. This is very useful to + * discern between the various possible successful answers produced by the + * combinations of @ref HAM_FIND_LT_MATCH and @ref HAM_FIND_GT_MATCH. + * + * @param cursor A valid Cursor handle + * @param key A pointer to a @ref ham_key_t structure. If this + * pointer is not NULL, the key of the new item is returned. + * Note that key->data will point to temporary data. This pointer + * will be invalidated by subsequent hamsterdb API calls. See + * @a HAM_KEY_USER_ALLOC on how to change this behaviour. + * @param record Optional pointer to a @ref ham_record_t structure. If this + * pointer is not NULL, the record of the new item is returned. + * Note that record->data will point to temporary data. This pointer + * will be invalidated by subsequent hamsterdb API calls. See + * @ref HAM_RECORD_USER_ALLOC on how to change this behaviour. + * @param flags Optional flags for searching, which can be combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_FIND_LT_MATCH </li> Cursor 'find' flag 'Less Than': the + * cursor is moved to point at the last record which' key + * is less than the specified key. When such a record cannot + * be located, an error is returned. + * <li>@ref HAM_FIND_GT_MATCH </li> Cursor 'find' flag 'Greater Than': + * the cursor is moved to point at the first record which' key is + * larger than the specified key. When such a record cannot be + * located, an error is returned. + * <li>@ref HAM_FIND_LEQ_MATCH </li> Cursor 'find' flag 'Less or EQual': + * the cursor is moved to point at the record which' key matches + * the specified key and when such a record is not available + * the cursor is moved to point at the last record which' key + * is less than the specified key. When such a record cannot be + * located, an error is returned. + * <li>@ref HAM_FIND_GEQ_MATCH </li> Cursor 'find' flag 'Greater or + * Equal': the cursor is moved to point at the record which' key + * matches the specified key and when such a record + * is not available the cursor is moved to point at the first + * record which' key is larger than the specified key. + * When such a record cannot be located, an error is returned. + * <li>@ref HAM_FIND_NEAR_MATCH </li> Cursor 'find' flag 'Any Near Or + * Equal': the cursor is moved to point at the record which' + * key matches the specified key and when such a record is + * not available the cursor is moved to point at either the + * last record which' key is less than the specified key or + * the first record which' key is larger than the specified + * key, whichever of these records is located first. + * When such records cannot be located, an error is returned. + * <li>@ref HAM_DIRECT_ACCESS </li> Only for In-Memory Databases and + * not if Transactions are enabled! + * Returns a direct pointer to the data blob stored by the + * hamsterdb engine. This pointer must not be resized or freed, + * but the data in this memory can be modified. + * </ul> + * + * <b>Remark</b> + * For Approximate Matching the returned match will either match the + * key exactly or is either the first key available above or below the + * given key when an exact match could not be found; 'find' does NOT + * spend any effort, in the sense of determining which of both is the + * 'nearest' to the given key, when both a key above and a key below the + * one given exist; 'find' will simply return the first of both found. + * As such, this flag is the simplest possible combination of the + * combined @ref HAM_FIND_LEQ_MATCH and @ref HAM_FIND_GEQ_MATCH flags. + * + * Note that these flags may be bitwise OR-ed to form functional combinations. + * + * @ref HAM_FIND_LEQ_MATCH, @ref HAM_FIND_GEQ_MATCH and + * @ref HAM_FIND_LT_MATCH, @ref HAM_FIND_GT_MATCH + * + * @return @ref HAM_SUCCESS upon success. Mind the remarks about the + * @a key flags being adjusted and the useful invocation of + * @ref ham_key_get_approximate_match_type() afterwards. + * @return @ref HAM_INV_PARAMETER if @a db, @a key or @a record is NULL + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_KEY_NOT_FOUND if no suitable @a key (record) exists + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS is specified, + * but the Database is not an In-Memory Database. + * @return @ref HAM_INV_PARAMETER if @a HAM_DIRECT_ACCESS and + * @a HAM_ENABLE_TRANSACTIONS were both specified. + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is set but record + * size is <= 8 or Transactions are enabled + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + * + * @sa HAM_KEY_USER_ALLOC + * @sa ham_key_t + * @sa HAM_RECORD_USER_ALLOC + * @sa ham_record_t + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_find(ham_cursor_t *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + +/* internal flag */ +#define HAM_FIND_EXACT_MATCH 0x4000 + +/** + * Cursor 'find' flag 'Less Than': return the nearest match below the + * given key, whether an exact match exists or not. + */ +#define HAM_FIND_LT_MATCH 0x1000 + +/** + * Cursor 'find' flag 'Greater Than': return the nearest match above the + * given key, whether an exact match exists or not. + */ +#define HAM_FIND_GT_MATCH 0x2000 + +/** + * Cursor 'find' flag 'Less or EQual': return the nearest match below the + * given key, when an exact match does not exist. + * + * May be combined with @ref HAM_FIND_GEQ_MATCH to accept any 'near' key, or + * you can use the @ref HAM_FIND_NEAR_MATCH constant as a shorthand for that. + */ +#define HAM_FIND_LEQ_MATCH (HAM_FIND_LT_MATCH | HAM_FIND_EXACT_MATCH) + +/** + * Cursor 'find' flag 'Greater or Equal': return the nearest match above + * the given key, when an exact match does not exist. + * + * May be combined with @ref HAM_FIND_LEQ_MATCH to accept any 'near' key, + * or you can use the @ref HAM_FIND_NEAR_MATCH constant as a shorthand for that. + */ +#define HAM_FIND_GEQ_MATCH (HAM_FIND_GT_MATCH | HAM_FIND_EXACT_MATCH) + +/** + * Cursor 'find' flag 'Any Near Or Equal': return a match directly below or + * above the given key, when an exact match does not exist. + * + * Be aware that the returned match will either match the key exactly or + * is either the first key available above or below the given key when an + * exact match could not be found; 'find' does NOT spend any effort, in the + * sense of determining which of both is the 'nearest' to the given key, + * when both a key above and a key below the one given exist; 'find' will + * simply return the first of both found. As such, this flag is the simplest + * possible combination of the combined @ref HAM_FIND_LEQ_MATCH and + * @ref HAM_FIND_GEQ_MATCH flags. + */ +#define HAM_FIND_NEAR_MATCH (HAM_FIND_LT_MATCH | HAM_FIND_GT_MATCH \ + | HAM_FIND_EXACT_MATCH) + +/** + * Inserts a Database item and points the Cursor to the inserted item + * + * This function inserts a key/record pair as a new Database item. + * If the key already exists in the Database, error @ref HAM_DUPLICATE_KEY + * is returned. + * + * If you wish to overwrite an existing entry specify the + * flag @ref HAM_OVERWRITE. The use of this flag is not allowed in combination + * with @ref HAM_DUPLICATE. + * + * If you wish to insert a duplicate key specify the flag @ref HAM_DUPLICATE. + * (In order to use duplicate keys, the Database has to be created with + * @ref HAM_ENABLE_DUPLICATE_KEYS.) + * By default, the duplicate key is inserted after all other duplicate keys + * (see @ref HAM_DUPLICATE_INSERT_LAST). This behaviour can be overwritten by + * specifying @ref HAM_DUPLICATE_INSERT_FIRST, @ref HAM_DUPLICATE_INSERT_BEFORE + * or @ref HAM_DUPLICATE_INSERT_AFTER. + * + * You can write only portions of the record by specifying the flag + * @ref HAM_PARTIAL. In this case, hamsterdb will write <b>partial_size</b> + * bytes of the record data at offset <b>partial_offset</b>. If necessary, the + * record data will grow. Gaps will be filled with null-bytes, if the record + * did not yet exist. + * + * @ref HAM_PARTIAL is not allowed if record->size is <= 8 or if Transactions + * are enabled. In such a case, @ref HAM_INV_PARAMETER is returned. + * + * Specify the flag @ref HAM_HINT_APPEND if you insert sequential data + * and the current @a key is greater than any other key in this Database. + * In this case hamsterdb will optimize the insert algorithm. hamsterdb will + * verify that this key is the greatest; if not, it will perform a normal + * insert. This flag is the default for Record Number Databases. + * + * Specify the flag @ref HAM_HINT_PREPEND if you insert sequential data + * and the current @a key is lower than any other key in this Database. + * In this case hamsterdb will optimize the insert algorithm. hamsterdb will + * verify that this key is the lowest; if not, it will perform a normal + * insert. + * + * After inserting, the Cursor will point to the new item. If inserting + * the item failed, the Cursor is not modified. + * + * Record Number Databases (created with @ref HAM_RECORD_NUMBER32 or + * @ref HAM_RECORD_NUMBER64) expect either an empty @a key (with a size of + * 0 and data pointing to NULL), or a user-supplied key (with key.flag + * @ref HAM_KEY_USER_ALLOC and a valid data pointer). + * If key.size is 0 and key.data is NULL, hamsterdb will temporarily + * allocate memory for key->data, which will then point to an 4-byte (or 8-byte) + * unsigned integer. + * + * @param cursor A valid Cursor handle + * @param key A valid key structure + * @param record A valid record structure + * @param flags Optional flags for inserting the item, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_OVERWRITE. If the @a key already exists, the record is + * overwritten. Otherwise, the key is inserted. Not allowed in + * combination with @ref HAM_DUPLICATE. + * <li>@ref HAM_DUPLICATE. If the @a key already exists, a duplicate + * key is inserted. Same as @ref HAM_DUPLICATE_INSERT_LAST. Not + * allowed in combination with @ref HAM_DUPLICATE. + * <li>@ref HAM_DUPLICATE_INSERT_BEFORE. If the @a key already exists, + * a duplicate key is inserted before the duplicate pointed + * to by the Cursor. Not allowed if duplicate sorting is enabled. + * <li>@ref HAM_DUPLICATE_INSERT_AFTER. If the @a key already exists, + * a duplicate key is inserted after the duplicate pointed + * to by the Cursor. Not allowed if duplicate sorting is enabled. + * <li>@ref HAM_DUPLICATE_INSERT_FIRST. If the @a key already exists, + * a duplicate key is inserted as the first duplicate of + * the current key. Not allowed if duplicate sorting is enabled. + * <li>@ref HAM_DUPLICATE_INSERT_LAST. If the @a key already exists, + * a duplicate key is inserted as the last duplicate of + * the current key. Not allowed if duplicate sorting is enabled. + * <li>@ref HAM_HINT_APPEND. Hints the hamsterdb engine that the + * current key will compare as @e larger than any key already + * existing in the Database. The hamsterdb engine will verify + * this postulation and when found not to be true, will revert + * to a regular insert operation as if this flag was not + * specified. The incurred cost then is only one additional key + * comparison. Mutually exclusive with flag @ref HAM_HINT_PREPEND. + * This is the default for Record Number Databases. + * <li>@ref HAM_HINT_PREPEND. Hints the hamsterdb engine that the + * current key will compare as @e lower than any key already + * existing in the Database. The hamsterdb engine will verify + * this postulation and when found not to be true, will revert + * to a regular insert operation as if this flag was not + * specified. The incurred cost then is only one additional key + * comparison. Mutually exclusive with flag @ref HAM_HINT_APPEND. + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a key or @a record is NULL + * @return @ref HAM_INV_PARAMETER if the Database is a Record Number Database + * and the key is invalid (see above) + * @return @ref HAM_INV_PARAMETER if @ref HAM_PARTIAL is set but record + * size is <= 8 or Transactions are enabled + * @return @ref HAM_INV_PARAMETER if the flags @ref HAM_OVERWRITE <b>and</b> + * @ref HAM_DUPLICATE were specified, or if @ref HAM_DUPLICATE + * was specified, but the Database was not created with + * flag @ref HAM_ENABLE_DUPLICATE_KEYS. + * @return @ref HAM_WRITE_PROTECTED if you tried to insert a key to a read-only + * Database. + * @return @ref HAM_INV_KEY_SIZE if the key size is different from + * the one specified with @a HAM_PARAM_KEY_SIZE + * @return @ref HAM_INV_RECORD_SIZE if the record size is different from + * the one specified with @a HAM_PARAM_RECORD_SIZE + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_insert(ham_cursor_t *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + +/** + * Erases the current key + * + * Erases a key from the Database. If the erase was + * successful, the Cursor is invalidated and does no longer point to + * any item. In case of an error, the Cursor is not modified. + * + * If the Database was opened with the flag @ref HAM_ENABLE_DUPLICATE_KEYS, + * this function erases only the duplicate item to which the Cursor refers. + * + * @param cursor A valid Cursor handle + * @param flags Unused, set to 0 + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if @a cursor is NULL + * @return @ref HAM_WRITE_PROTECTED if you tried to erase a key from a read-only + * Database + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_erase(ham_cursor_t *cursor, uint32_t flags); + +/** + * Returns the number of duplicate keys + * + * Returns the number of duplicate keys of the item to which the + * Cursor currently refers. + * Returns 1 if the key has no duplicates. + * + * @param cursor A valid Cursor handle + * @param count Returns the number of duplicate keys + * @param flags Optional flags; unused, set to 0. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_INV_PARAMETER if @a cursor or @a count is NULL + * @return @ref HAM_TXN_CONFLICT if the same key was inserted in another + * Transaction which was not yet committed or aborted + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_get_duplicate_count(ham_cursor_t *cursor, + uint32_t *count, uint32_t flags); + +/** + * Returns the current cursor position in the duplicate list + * + * Returns the position in the duplicate list of the current key. The position + * is 0-based. + * + * @param cursor A valid Cursor handle + * @param position Returns the duplicate position + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_INV_PARAMETER if @a cursor or @a position is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_get_duplicate_position(ham_cursor_t *cursor, + uint32_t *position); + +/** + * Returns the record size of the current key + * + * Returns the record size of the item to which the Cursor currently refers. + * + * @param cursor A valid Cursor handle + * @param size Returns the record size, in bytes + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_INV_PARAMETER if @a cursor or @a size is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_get_record_size(ham_cursor_t *cursor, uint64_t *size); + +/** + * Closes a Database Cursor + * + * Closes a Cursor and frees allocated memory. All Cursors + * should be closed before closing the Database (see @ref ham_db_close). + * + * @param cursor A valid Cursor handle + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_CURSOR_IS_NIL if the Cursor does not point to an item + * @return @ref HAM_INV_PARAMETER if @a cursor is NULL + * + * @sa ham_db_close + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_close(ham_cursor_t *cursor); + +/** + * @} + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HAM_HAMSTERDB_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.hpp b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.hpp new file mode 100644 index 0000000000..68892ac2d7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb.hpp @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file hamsterdb.hpp + * @author Christoph Rupp, chris@crupp.de + * @version 2.1.10 + * + * This C++ wrapper class is a very tight wrapper around the C API. It does + * not attempt to be STL compatible. + * + * All functions throw exceptions of class @sa ham::error in case of an error. + * Please refer to the C API documentation for more information. You can find + * it here: http://hamsterdb.com/?page=doxygen&module=globals.html + * + */ + +#ifndef HAM_HAMSTERDB_HPP +#define HAM_HAMSTERDB_HPP + +#include <ham/hamsterdb.h> +#include <ham/hamsterdb_int.h> +#include <cstring> +#include <vector> + +#if defined(_MSC_VER) && defined(_DEBUG) && !defined(_CRTDBG_MAP_ALLOC) +# define _CRTDBG_MAP_ALLOC +# include <crtdbg.h> +#endif + +/** + * @defgroup ham_cpp hamsterdb C++ API wrapper + * @{ + */ + +/** + * The global hamsterdb namespace. + */ +namespace hamsterdb { + +class txn; +class db; +class env; + +/** + * An error class. + * + * The hamsterdb C++ API throws this class as Exceptions. + */ +class error { + public: + /** Constructor */ + error(ham_status_t st) + : m_errno(st) { + }; + + /** Returns the error code. */ + ham_status_t get_errno() const { + return (m_errno); + } + + /** Returns an English error description. */ + const char *get_string() const { + return (ham_strerror(m_errno)); + } + +private: + ham_status_t m_errno; +}; + +/** + * A key class. + * + * This class wraps structures of type ham_key_t. + */ +class key { + public: + /** Constructor */ + key(void *data = 0, uint16_t size = 0, uint32_t flags = 0) { + memset(&m_key, 0, sizeof(m_key)); + m_key.data = data; + m_key.size = size; + m_key.flags = flags; + if (m_key.size != size) // check for overflow + throw error(HAM_INV_KEYSIZE); + } + + /** Copy constructor. */ + key(const key &other) + : m_key(other.m_key) { + } + + /** Assignment operator. */ + key &operator=(const key &other) { + if (&other != this) + m_key = other.m_key; + return (*this); + } + + /** Returns the key data. */ + void *get_data() const { + return (m_key.data); + } + + /** Sets the key data. */ + void set_data(void *data) { + m_key.data = data; + } + + /** Returns the size of the key. */ + uint16_t get_size() const { + return (m_key.size); + } + + /** Sets the size of the key. */ + void set_size(uint16_t size) { + m_key.size = size; + } + + /** Template assignment */ + template <class T> + void set(T &t) { + set_data(&t); + set_size(sizeof(t)); + } + + /** Returns the flags of the key. */ + uint32_t get_flags() const { + return (m_key.flags); + } + + /** Sets the flags of the key. */ + void set_flags(uint32_t flags) { + m_key.flags = flags; + } + + /** Returns a pointer to the internal ham_key_t structure. */ + ham_key_t *get_handle() { + return (&m_key); + } + + /** Returns 'sign' of Approximate Match */ + int get_approximate_match_type() { + return (ham_key_get_approximate_match_type(&m_key)); + } + +private: + ham_key_t m_key; +}; + +/** + * A record class. + * + * This class wraps structures of type ham_record_t. + */ +class record { + public: + /** Constructor */ + record(void *data = 0, uint32_t size = 0, uint32_t flags = 0) { + memset(&m_rec, 0, sizeof(m_rec)); + m_rec.data = data; + m_rec.size = size; + m_rec.flags = flags; + } + + /** Copy constructor. */ + record(const record &other) + : m_rec(other.m_rec) { + } + + /** Assignment operator. */ + record &operator=(const record &other) { + m_rec = other.m_rec; + return (*this); + } + + /** Returns the record data. */ + void *get_data() const { + return (m_rec.data); + } + + /** Sets the record data. */ + void set_data(void *data) { + m_rec.data = data; + } + + /** Returns the size of the record. */ + uint32_t get_size() const { + return (m_rec.size); + } + + /** Sets the size of the record. */ + void set_size(uint32_t size) { + m_rec.size = size; + } + + /** Returns the flags of the record. */ + uint32_t get_flags() const { + return (m_rec.flags); + } + + /** Sets the flags of the record. */ + void set_flags(uint32_t flags) { + m_rec.flags = flags; + } + + /** Returns a pointer to the internal ham_record_t structure. */ + ham_record_t *get_handle() { + return (&m_rec); + } + + protected: + ham_record_t m_rec; +}; + + +/** + * A Transaction class + * + * This class wraps structures of type ham_txn_t. + */ +class txn { + public: + /** Constructor */ + txn(ham_txn_t *t = 0) + : m_txn(t) { + } + + /** Abort the Transaction */ + void abort() { + ham_status_t st = ham_txn_abort(m_txn, 0); + if (st) + throw error(st); + } + + /** Commit the Transaction */ + void commit() { + ham_status_t st = ham_txn_commit(m_txn, 0); + if (st) + throw error(st); + } + + std::string get_name() { + const char *p = ham_txn_get_name(m_txn); + return (p ? p : ""); + } + + /** Returns a pointer to the internal ham_txn_t structure. */ + ham_txn_t *get_handle() { + return (m_txn); + } + + protected: + ham_txn_t *m_txn; +}; + + +/** + * A Database class. + * + * This class wraps the ham_db_t Database handles. + */ +class db { + public: + /** Set error handler function. */ + static void set_errhandler(ham_errhandler_fun f) { + ham_set_errhandler(f); + } + + /** Retrieves the hamsterdb library version. */ + static void get_version(uint32_t *major, uint32_t *minor, + uint32_t *revision) { + ham_get_version(major, minor, revision); + } + + /** Constructor */ + db() + : m_db(0) { + } + + /** Destructor - automatically closes the Database, if necessary. */ + ~db() { + close(); + } + + /** + * Assignment operator. + * + * <b>Important!</b> This operator transfers the ownership of the + * Database handle. + */ + db &operator=(const db &other) { + db &rhs = (db &)other; + if (this == &other) + return (*this); + close(); + m_db = rhs.m_db; + rhs.m_db = 0; + return (*this); + } + + /** Returns the last Database error. */ + ham_status_t get_error() { + return (ham_db_get_error(m_db)); + } + + /** Sets the comparison function. */ + void set_compare_func(ham_compare_func_t foo) { + ham_status_t st = ham_db_set_compare_func(m_db, foo); + if (st) + throw error(st); + } + + /** Finds a record by looking up the key. */ + record find(txn *t, key *k, uint32_t flags = 0) { + record r; + ham_status_t st = ham_db_find(m_db, + t ? t->get_handle() : 0, + k ? k->get_handle() : 0, + r.get_handle(), flags); + if (st) + throw error(st); + return (r); + } + + /** Finds a record by looking up the key. */ + record &find(txn *t, key *k, record *r, uint32_t flags = 0) { + ham_status_t st = ham_db_find(m_db, + t ? t->get_handle() : 0, + k ? k->get_handle() : 0, + r->get_handle(), flags); + if (st) + throw error(st); + return (*r); + } + + /** Finds a record by looking up the key. */ + record find(key *k, uint32_t flags = 0) { + return (find(0, k, flags)); + } + + /** Inserts a key/record pair. */ + void insert(txn *t, key *k, record *r, uint32_t flags = 0) { + ham_status_t st = ham_db_insert(m_db, + t ? t->get_handle() : 0, + k ? k->get_handle() : 0, + r ? r->get_handle() : 0, flags); + if (st) + throw error(st); + } + + /** Inserts a key/record pair. */ + void insert(key *k, record *r, uint32_t flags=0) { + insert(0, k, r, flags); + } + + /** Erases a key/record pair. */ + void erase(key *k, uint32_t flags = 0) { + erase(0, k, flags); + } + + /** Erases a key/record pair. */ + void erase(txn *t, key *k, uint32_t flags = 0) { + ham_status_t st = ham_db_erase(m_db, + t ? t->get_handle() : 0, + k ? k->get_handle() : 0, flags); + if (st) + throw error(st); + } + + /** Returns number of items in the Database. */ + uint64_t get_key_count(ham_txn_t *txn = 0, uint32_t flags = 0) { + uint64_t count = 0; + ham_status_t st = ham_db_get_key_count(m_db, txn, flags, &count); + if (st) + throw error(st); + return (count); + } + + /** Retrieves Database parameters. */ + void get_parameters(ham_parameter_t *param) { + ham_status_t st = ham_db_get_parameters(m_db, param); + if (st) + throw error(st); + } + + /** Closes the Database. */ + void close(uint32_t flags = 0) { + if (!m_db) + return; + // disable auto-cleanup; all objects will be destroyed when + // going out of scope + flags &= ~HAM_AUTO_CLEANUP; + ham_status_t st = ham_db_close(m_db, flags); + if (st) + throw error(st); + m_db = 0; + } + + /** Returns a pointer to the internal ham_db_t structure. */ + ham_db_t *get_handle() { + return (m_db); + } + +protected: + friend class env; + + /* Copy Constructor. Is protected and should not be used. */ + db(ham_db_t *db) + : m_db(db) { + } + + private: + ham_db_t *m_db; +}; + + +/** + * A Database Cursor. + * + * This class wraps the ham_cursor_t Cursor handles. + */ +class cursor { + public: + /** Constructor */ + cursor(db *db = 0, txn *t = 0, uint32_t flags = 0) + : m_cursor(0) { + create(db, t, flags); + } + + /** Constructor */ + cursor(txn *t, db *db = 0, uint32_t flags = 0) + : m_cursor(0) { + create(db, t, flags); + } + + /** Destructor - automatically closes the Cursor, if necessary. */ + ~cursor() { + close(); + } + + /** Creates a new Cursor. */ + void create(db *db, txn *t = 0, uint32_t flags = 0) { + if (m_cursor) + close(); + if (db) { + ham_status_t st = ham_cursor_create(&m_cursor, db->get_handle(), + t ? t->get_handle() : 0, flags); + if (st) + throw error(st); + } + } + + /** Clones the Cursor. */ + cursor clone() { + ham_cursor_t *dest; + ham_status_t st = ham_cursor_clone(m_cursor, &dest); + if (st) + throw error(st); + return (cursor(dest)); + } + + /** Moves the Cursor, and retrieves the key/record of the new position. */ + void move(key *k, record *r, uint32_t flags = 0) { + ham_status_t st = ham_cursor_move(m_cursor, k ? k->get_handle() : 0, + r ? r->get_handle() : 0, flags); + if (st) + throw error(st); + } + + /** Moves the Cursor to the first Database element. */ + void move_first(key *k = 0, record *r = 0) { + move(k, r, HAM_CURSOR_FIRST); + } + + /** Moves the Cursor to the last Database element. */ + void move_last(key *k = 0, record *r = 0) { + move(k, r, HAM_CURSOR_LAST); + } + + /** Moves the Cursor to the next Database element. */ + void move_next(key *k = 0, record *r = 0) { + move(k, r, HAM_CURSOR_NEXT); + } + + /** Moves the Cursor to the previous Database element. */ + void move_previous(key *k = 0, record *r = 0) { + move(k, r, HAM_CURSOR_PREVIOUS); + } + + /** Overwrites the current record. */ + void overwrite(record *r, uint32_t flags = 0) { + ham_status_t st = ham_cursor_overwrite(m_cursor, + r ? r->get_handle() : 0, flags); + if (st) + throw error(st); + } + + /** Finds a key. */ + void find(key *k, record *r = 0, uint32_t flags = 0) { + ham_status_t st = ham_cursor_find(m_cursor, k->get_handle(), + (r ? r->get_handle() : 0), flags); + if (st) + throw error(st); + } + + /** Inserts a key/record pair. */ + void insert(key *k, record *r, uint32_t flags = 0) { + ham_status_t st = ham_cursor_insert(m_cursor, k ? k->get_handle() : 0, + r ? r->get_handle() : 0, flags); + if (st) + throw error(st); + } + + /** Erases the current key/record pair. */ + void erase(uint32_t flags = 0) { + ham_status_t st = ham_cursor_erase(m_cursor, flags); + if (st) + throw error(st); + } + + /** Returns the number of duplicate keys. */ + uint32_t get_duplicate_count(uint32_t flags = 0) { + uint32_t c; + ham_status_t st = ham_cursor_get_duplicate_count(m_cursor, &c, flags); + if (st) + throw error(st); + return (c); + } + + /** Returns the size of the current record. */ + uint64_t get_record_size() { + uint64_t s; + ham_status_t st = ham_cursor_get_record_size(m_cursor, &s); + if (st) + throw error(st); + return (s); + } + + /** Closes the Cursor. */ + void close() { + if (!m_cursor) + return; + ham_status_t st = ham_cursor_close(m_cursor); + if (st) + throw error(st); + m_cursor = 0; + } + + protected: + /* Copy Constructor. Is protected and should not be used. */ + cursor(ham_cursor_t *c) { + m_cursor = c; + } + + private: + ham_cursor_t *m_cursor; +}; + +/** + * An Environment class. + * + * This class wraps the ham_env_t structure. + */ +class env { + public: + /** Constructor */ + env() + : m_env(0) { + } + + /** Destructor - automatically closes the Cursor, if necessary. */ + ~env() { + close(); + } + + /** Creates a new Environment. */ + void create(const char *filename, uint32_t flags = 0, + uint32_t mode = 0644, const ham_parameter_t *param = 0) { + ham_status_t st = ham_env_create(&m_env, filename, flags, mode, param); + if (st) + throw error(st); + } + + /** Opens an existing Environment. */ + void open(const char *filename, uint32_t flags = 0, + const ham_parameter_t *param = 0) { + ham_status_t st = ham_env_open(&m_env, filename, flags, param); + if (st) + throw error(st); + } + + /** Flushes the Environment to disk. */ + void flush(uint32_t flags = 0) { + ham_status_t st = ham_env_flush(m_env, flags); + if (st) + throw error(st); + } + + /** Creates a new Database in the Environment. */ + db create_db(uint16_t name, uint32_t flags = 0, + const ham_parameter_t *param = 0) { + ham_db_t *dbh; + + ham_status_t st = ham_env_create_db(m_env, &dbh, name, flags, param); + if (st) + throw error(st); + + return (hamsterdb::db(dbh)); + } + + /** Opens an existing Database in the Environment. */ + db open_db(uint16_t name, uint32_t flags = 0, + const ham_parameter_t *param = 0) { + ham_db_t *dbh; + + ham_status_t st = ham_env_open_db(m_env, &dbh, name, flags, param); + if (st) + throw error(st); + + return (hamsterdb::db(dbh)); + } + + /** Renames an existing Database in the Environment. */ + void rename_db(uint16_t oldname, uint16_t newname, uint32_t flags = 0) { + ham_status_t st = ham_env_rename_db(m_env, oldname, newname, flags); + if (st) + throw error(st); + } + + /** Deletes a Database from the Environment. */ + void erase_db(uint16_t name, uint32_t flags = 0) { + ham_status_t st = ham_env_erase_db(m_env, name, flags); + if (st) + throw error(st); + } + + /** Begin a new Transaction */ + txn begin(const char *name = 0) { + ham_txn_t *h; + ham_status_t st = ham_txn_begin(&h, m_env, name, 0, 0); + if (st) + throw error(st); + return (txn(h)); + } + + + /** Closes the Environment. */ + void close(uint32_t flags = 0) { + if (!m_env) + return; + // disable auto-cleanup; all objects will be destroyed when + // going out of scope + flags &= ~HAM_AUTO_CLEANUP; + ham_status_t st = ham_env_close(m_env, flags); + if (st) + throw error(st); + m_env = 0; + } + + /** Retrieves Environment parameters. */ + void get_parameters(ham_parameter_t *param) { + ham_status_t st = ham_env_get_parameters(m_env, param); + if (st) + throw error(st); + } + + /** Get all Database names. */ + std::vector<uint16_t> get_database_names() { + uint32_t count = 32; + ham_status_t st; + std::vector<uint16_t> v(count); + + for (;;) { + st = ham_env_get_database_names(m_env, &v[0], &count); + if (!st) + break; + if (st && st!=HAM_LIMITS_REACHED) + throw error(st); + count += 16; + v.resize(count); + } + + v.resize(count); + return (v); + } + + private: + ham_env_t *m_env; +}; + +} // namespace hamsterdb + +/** + * @} + */ + +#endif // HAMSTERDB_HPP diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_int.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_int.h new file mode 100644 index 0000000000..ec05ece264 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_int.h @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file hamsterdb_int.h + * @brief Internal hamsterdb Embedded Storage functions. + * @author Christoph Rupp, chris@crupp.de + * + * Please be aware that the interfaces in this file are mostly for internal + * use. Unlike those in hamsterdb.h they are not stable and can be changed + * with every new version. + * + */ + +#ifndef HAM_HAMSTERDB_INT_H +#define HAM_HAMSTERDB_INT_H + +#include <ham/hamsterdb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup ham_extended_api hamsterdb Enhanced API + * @{ + */ + +/** get the (non-persisted) flags of a key */ +#define ham_key_get_intflags(key) (key)->_flags + +/** + * set the flags of a key + * + * Note that the ham_find/ham_cursor_find/ham_cursor_find_ex flags must + * be defined such that those can peacefully co-exist with these; that's + * why those public flags start at the value 0x1000 (4096). + */ +#define ham_key_set_intflags(key, f) (key)->_flags=(f) + +/** + * Verifies the integrity of the Database + * + * This function is only interesting if you want to debug hamsterdb. + * + * @param db A valid Database handle + * @param flags Optional flags for the integrity check, combined with + * bitwise OR. Possible flags are: + * <ul> + * <li>@ref HAM_PRINT_GRAPH</li> Prints the Btree as a graph; stores + * the image as "graph.png" in the current working directory. It uses + * the "dot" tool from graphviz to generate the image. + * This functionality is only available in DEBUG builds! + * </ul> + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INTEGRITY_VIOLATED if the Database is broken + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_check_integrity(ham_db_t *db, uint32_t flags); + +/** Flag for ham_db_check_integrity */ +#define HAM_PRINT_GRAPH 1 + +/** + * Set a user-provided context pointer + * + * This function sets a user-provided context pointer. This can be any + * arbitrary pointer; it is stored in the Database handle and can be + * retrieved with @a ham_get_context_data. It is mainly used by Wrappers + * and language bindings. + * + * @param db A valid Database handle + * @param data The pointer to the context data + */ +HAM_EXPORT void HAM_CALLCONV +ham_set_context_data(ham_db_t *db, void *data); + +/** + * Retrieves a user-provided context pointer + * + * This function retrieves a user-provided context pointer. This can be any + * arbitrary pointer which was previously stored with @a ham_set_context_data. + * + * @param db A valid Database handle + * @param dont_lock Whether the Environment mutex should be locked or not + * this is used to avoid recursive locks when retrieving the context + * data in a compare function + * + * @return The pointer to the context data + */ +HAM_EXPORT void * HAM_CALLCONV +ham_get_context_data(ham_db_t *db, ham_bool_t dont_lock); + +/** + * Retrieves the Database handle of a Cursor + * + * @param cursor A valid Cursor handle + * + * @return @a The Database handle of @a cursor + */ +HAM_EXPORT ham_db_t * HAM_CALLCONV +ham_cursor_get_database(ham_cursor_t *cursor); + +typedef struct min_max_avg_u32_t { + uint32_t min; + uint32_t max; + uint32_t avg; + uint32_t _total; /* for calculating the average */ + uint32_t _instances; /* for calculating the average */ +} min_max_avg_u32_t; + +/* btree metrics */ +typedef struct btree_metrics_t { + /* the database name of the btree */ + uint16_t database_name; + + /* number of pages */ + uint64_t number_of_pages; + + /* number of keys */ + uint64_t number_of_keys; + + /* total btree space, including overhead */ + uint64_t total_btree_space; + + /* static overhead per page */ + uint32_t overhead_per_page; + + /* number of keys stored per page (w/o duplicates) */ + min_max_avg_u32_t keys_per_page; + + /* payload storage assigned to the KeyLists */ + min_max_avg_u32_t keylist_ranges; + + /* payload storage assigned to the RecordLists */ + min_max_avg_u32_t recordlist_ranges; + + /* storage assigned to the Indices (if available) */ + min_max_avg_u32_t keylist_index; + + /* storage assigned to the Indices (if available) */ + min_max_avg_u32_t recordlist_index; + + /* unused storage (i.e. gaps between pages, underfilled blocks etc) */ + min_max_avg_u32_t keylist_unused; + + /* unused storage (i.e. gaps between pages, underfilled blocks etc) */ + min_max_avg_u32_t recordlist_unused; + + /* number of blocks per page (if available) */ + min_max_avg_u32_t keylist_blocks_per_page; + + /* block sizes (if available) */ + min_max_avg_u32_t keylist_block_sizes; +} btree_metrics_t; + +/** + * Retrieves collected metrics from the hamsterdb Environment. Used mainly + * for testing. + * See below for the structure with the currently available metrics. + * This structure will change a lot; the first field is a version indicator + * that applications can use to verify that the structure layout is compatible. + * + * These metrics are NOT persisted to disk. + * + * Metrics marked "global" are stored globally and shared between multiple + * Environments. + */ +#define HAM_METRICS_VERSION 9 + +typedef struct ham_env_metrics_t { + /* the version indicator - must be HAM_METRICS_VERSION */ + uint16_t version; + + /* number of total allocations for the whole lifetime of the process */ + uint64_t mem_total_allocations; + + /* currently active allocations for the whole process */ + uint64_t mem_current_allocations; + + /* current amount of memory allocated and tracked by the process + * (excludes memory used by the kernel or not allocated with + * malloc/free) */ + uint64_t mem_current_usage; + + /* peak usage of memory (for the whole process) */ + uint64_t mem_peak_usage; + + /* the heap size of this process */ + uint64_t mem_heap_size; + + /* amount of pages fetched from disk */ + uint64_t page_count_fetched; + + /* amount of pages written to disk */ + uint64_t page_count_flushed; + + /* number of index pages in this Environment */ + uint64_t page_count_type_index; + + /* number of blob pages in this Environment */ + uint64_t page_count_type_blob; + + /* number of page-manager pages in this Environment */ + uint64_t page_count_type_page_manager; + + /* number of successful freelist hits */ + uint64_t freelist_hits; + + /* number of freelist misses */ + uint64_t freelist_misses; + + /* number of successful cache hits */ + uint64_t cache_hits; + + /* number of cache misses */ + uint64_t cache_misses; + + /* number of blobs allocated */ + uint64_t blob_total_allocated; + + /* number of blobs read */ + uint64_t blob_total_read; + + /* (global) number of btree page splits */ + uint64_t btree_smo_split; + + /* (global) number of btree page merges */ + uint64_t btree_smo_merge; + + /* (global) number of extended keys */ + uint64_t extended_keys; + + /* (global) number of extended duplicate tables */ + uint64_t extended_duptables; + + /* number of bytes that the log/journal flushes to disk */ + uint64_t journal_bytes_flushed; + + /* PRO: log/journal bytes before compression */ + uint64_t journal_bytes_before_compression; + + /* PRO: log/journal bytes after compression */ + uint64_t journal_bytes_after_compression; + + /* PRO: record bytes before compression */ + uint64_t record_bytes_before_compression; + + /* PRO: record bytes after compression */ + uint64_t record_bytes_after_compression; + + /* PRO: key bytes before compression */ + uint64_t key_bytes_before_compression; + + /* PRO: key bytes after compression */ + uint64_t key_bytes_after_compression; + + /* PRO: set to the max. SIMD lane width (0 if SIMD is not available) */ + int simd_lane_width; + + /* btree metrics for leaf nodes */ + btree_metrics_t btree_leaf_metrics; + + /* btree metrics for internal nodes */ + btree_metrics_t btree_internal_metrics; + +} ham_env_metrics_t; + +/** + * Retrieves the current metrics from an Environment + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_get_metrics(ham_env_t *env, ham_env_metrics_t *metrics); + +/** + * Returns @ref HAM_TRUE if this hamsterdb library was compiled with debug + * diagnostics, checks and asserts + */ +HAM_EXPORT ham_bool_t HAM_CALLCONV +ham_is_debug(); + +/** + * Returns @ref HAM_TRUE if this hamsterdb library is the commercial + * closed-source "hamsterdb pro" edition + */ +HAM_EXPORT ham_bool_t HAM_CALLCONV +ham_is_pro(); + +/** + * Returns the end time of the evaluation period, if this is an evaluation + * license of the commercial closed-source "hamsterdb pro"; + * returns 0 otherwise + */ +HAM_EXPORT uint32_t HAM_CALLCONV +ham_is_pro_evaluation(); + +/** + * @} + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HAM_HAMSTERDB_INT_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_ola.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_ola.h new file mode 100644 index 0000000000..f65b98b8b1 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_ola.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file hamsterdb_hola.h + * @brief Include file for hamsterdb OnLine Analytical functions + * @author Christoph Rupp, chris@crupp.de + * @version 2.1.10 + * + * This API is EXPERIMENTAL!! The interface is not yet stable. + */ + +#ifndef HAM_HAMSTERDB_OLA_H +#define HAM_HAMSTERDB_OLA_H + +#include <ham/hamsterdb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A predicate function with context parameters returning a bool value. + * + * The predicate function is applied to various analytical functions + * of this API and is generally used to select keys where a predicate applies. + */ +typedef struct { + /** A function pointer; receives a key, returns a bool */ + ham_bool_t (*predicate_func)(const void *key_data, uint16_t key_size, + void *context); + + /** User-supplied context data */ + void *context; + +} hola_bool_predicate_t; + + +/** + * A structure which returns the result of an operation. + * + * For now, the result is either a @a uint64_t counter or a @a double value. + * The @a type parameter specifies which one is used; @a type's value is + * one of @a HAM_TYPE_UINT64 or @a HAM_TYPE_REAL64. + */ +typedef struct { + union { + /** The result as a 64bit unsigned integer */ + uint64_t result_u64; + + /** The result as a 64bit real */ + double result_double; + } u; + + /** The actual type in the union - one of the @a HAM_TYPE_* macros */ + int type; + +} hola_result_t; + + +/** + * Counts the keys in a Database + * + * This is a non-distinct count. If the Database has duplicate keys then + * they are included in the count. + * + * The actual count is returned in @a result->u.result_u64. @a result->type + * is set to @a HAM_TYPE_U64. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_count(ham_db_t *db, ham_txn_t *txn, hola_result_t *result); + +/** + * Selectively counts the keys in a Database + * + * This is a non-distinct count. If the Database has duplicate keys then + * they are included in the count. The predicate function is applied to + * each key. If it returns true then the key (and its duplicates) is included + * in the count; otherwise the key is ignored. + * + * The actual count is returned in @a result->u.result_u64. @a result->type + * is set to @a HAM_TYPE_U64. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_count_if(ham_db_t *db, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result); + +/** + * Counts the distinct keys in a Database + * + * This is a distinct count. If the Database has duplicate keys then + * they are not included in the count. + * + * The actual count is returned in @a result->u.result_u64. @a result->type + * is set to @a HAM_TYPE_U64. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_count_distinct(ham_db_t *db, ham_txn_t *txn, hola_result_t *result); + +/** + * Selectively counts the distinct keys in a Database + * + * This is a distinct count. If the Database has duplicate keys then + * they are not included in the count. The predicate function is applied to + * each key. If it returns true then the key is included in the count; + * otherwise the key is ignored. + * + * The actual count is returned in @a result->u.result_u64. @a result->type + * is set to @a HAM_TYPE_U64. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_count_distinct_if(ham_db_t *db, ham_txn_t *txn, + hola_bool_predicate_t *pred, hola_result_t *result); + +/** + * Calculates the average of all keys. + * + * This is a non-distinct function and includes all duplicate keys. + * + * Internally, a 64bit counter is used for the calculation. This function + * does not protect against an overflow of this counter. + * + * The keys in the database (@a db) have to be numeric, which means that + * the Database's type must be one of @a HAM_TYPE_UINT8, @a HAM_TYPE_UINT16, + * HAM_TYPE_UINT32, @a HAM_TYPE_UINT64, @a HAM_TYPE_REAL32 or + * @a HAM_TYPE_REAL64. + * + * The actual result is returned in @a result->u.result_u64 or + * @a result->u.result_double, depending on the Database's configuration. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + * @return @ref HAM_INV_PARAMETER if the database is not numeric + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_average(ham_db_t *db, ham_txn_t *txn, hola_result_t *result); + +/** + * Calculates the average of all keys where a predicate applies. + * + * This is a non-distinct function and includes all duplicate keys for which + * the predicate function returns true. + * + * Internally, a 64bit counter is used for the calculation. This function + * does not protect against an overflow of this counter. + * + * The keys in the database (@a db) have to be numeric, which means that + * the Database's type must be one of @a HAM_TYPE_UINT8, @a HAM_TYPE_UINT16, + * HAM_TYPE_UINT32, @a HAM_TYPE_UINT64, @a HAM_TYPE_REAL32 or + * @a HAM_TYPE_REAL64. + * + * The actual result is returned in @a result->u.result_u64 or + * @a result->u.result_double, depending on the Database's configuration. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + * @return @ref HAM_INV_PARAMETER if the database is not numeric + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_average_if(ham_db_t *db, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result); + +/** + * Calculates the sum of all keys. + * + * This is a non-distinct function and includes all duplicate keys. + * + * Internally, a 64bit counter is used for the calculation. This function + * does not protect against an overflow of this counter. + * + * The keys in the database (@a db) have to be numeric, which means that + * the Database's type must be one of @a HAM_TYPE_UINT8, @a HAM_TYPE_UINT16, + * HAM_TYPE_UINT32, @a HAM_TYPE_UINT64, @a HAM_TYPE_REAL32 or + * @a HAM_TYPE_REAL64. + * + * The actual result is returned in @a result->u.result_u64 or + * @a result->u.result_double, depending on the Database's configuration. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + * @return @ref HAM_INV_PARAMETER if the database is not numeric + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_sum(ham_db_t *db, ham_txn_t *txn, hola_result_t *result); + +/** + * Calculates the sum of all keys where a predicate applies. + * + * This is a non-distinct function and includes all duplicate keys for which + * the predicate function returns true. + * + * Internally, a 64bit counter is used for the calculation. This function + * does not protect against an overflow of this counter. + * + * The keys in the database (@a db) have to be numeric, which means that + * the Database's type must be one of @a HAM_TYPE_UINT8, @a HAM_TYPE_UINT16, + * HAM_TYPE_UINT32, @a HAM_TYPE_UINT64, @a HAM_TYPE_REAL32 or + * @a HAM_TYPE_REAL64. + * + * The actual result is returned in @a result->u.result_u64 or + * @a result->u.result_double, depending on the Database's configuration. + * + * @return @ref HAM_SUCCESS upon success + * @return @ref HAM_INV_PARAMETER if one of the parameters is NULL + * @return @ref HAM_INV_PARAMETER if the database is not numeric + */ +HAM_EXPORT ham_status_t HAM_CALLCONV +hola_sum_if(ham_db_t *db, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result); + +/** + * @} + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HAM_HAMSTERDB_OLA_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_srv.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_srv.h new file mode 100644 index 0000000000..83ffef8f2e --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/hamsterdb_srv.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAM_HAMSTERDB_SRV_H +#define HAM_HAMSTERDB_SRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <ham/hamsterdb.h> + +/** + * @defgroup ham_server hamsterdb Embedded Server + * @{ + */ + +/** + * A configuration structure + * + * It is always recommended to initialize the full structure with zeroes + * before using it. + */ +typedef struct { + /** The server port */ + uint16_t port; + + /* Path of the access log, or NULL if no log should be written + * - currently NOT USED! */ + const char *access_log_path; + + /** Path of the error log, or NULL if no log should be written + * - currently NOT USED! */ + const char *error_log_path; + +} ham_srv_config_t; + +/** + * A server handle + */ +struct ham_srv_t; +typedef struct ham_srv_t ham_srv_t; + +/** + * Initialize the server + * + * This function initializes a ham_srv_t handle and starts the hamsterdb + * database server on the port specified in the configuration object. + * + * @param config A configuration structure + * @param srv A pointer to a ham_srv_t pointer; will be allocated + * if this function returns successfully + * + * @return HAM_SUCCESS on success + * @return HAM_OUT_OF_MEMORY if memory could not be allocated + */ +extern ham_status_t +ham_srv_init(ham_srv_config_t *config, ham_srv_t **srv); + +/** + * Add a hamsterdb Environment + * + * This function adds a new hamsterdb Environment to the server. The + * Environment has to be initialized properly by the caller. It will be + * served at ham://localhost:port/urlname, where @a port was specified + * for @ref ham_srv_init and @a urlname is the third parameter to this + * function. + * + * A client accessing this Environment will specify this URL as a filename, + * and hamsterdb will transparently connect to this server. + * + * @param srv A valid ham_srv_t handle + * @param env A valid hamsterdb Environment handle + * @param urlname URL of this Environment + * + * @return HAM_SUCCESS on success + * @return HAM_LIMITS_REACHED if more than the max. number of Environments + * were added (default limit: 128) + */ +extern ham_status_t +ham_srv_add_env(ham_srv_t *srv, ham_env_t *env, const char *urlname); + +/* + * Release memory and clean up + * + * @param srv A valid ham_srv_t handle + * + * @warning + * This function will not close open handles (i.e. of Databases, Cursors + * or Transactions). The caller has to close the remaining Environment + * handles (@see ham_env_close). + */ +extern void +ham_srv_close(ham_srv_t *srv); + +/** + * @} + */ + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HAM_HAMSTERDB_SRV_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/msstdint.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/msstdint.h new file mode 100644 index 0000000000..4fe0ef9a9b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/msstdint.h @@ -0,0 +1,259 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#if _MSC_VER >= 1600 // [ +#include <stdint.h> +#else // ] _MSC_VER >= 1600 [ + +#include <limits.h> + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include <wchar.h> +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>. +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/plugins/Dbx_kv/src/hamsterdb/include/ham/types.h b/plugins/Dbx_kv/src/hamsterdb/include/ham/types.h new file mode 100644 index 0000000000..54d75aa7e0 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/include/ham/types.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file types.h + * @brief Portable typedefs for hamsterdb Embedded Storage. + * @author Christoph Rupp, chris@crupp.de + * + */ + +#ifndef HAM_TYPES_H +#define HAM_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Check the operating system and word size + */ +#ifdef WIN32 +# undef HAM_OS_WIN32 +# define HAM_OS_WIN32 1 +# ifdef WIN64 +# undef HAM_64BIT +# define HAM_64BIT 1 +# elif WIN32 +# undef HAM_32BIT +# define HAM_32BIT 1 +# else +# error "Neither WIN32 nor WIN64 defined!" +# endif +#else /* posix? */ +# undef HAM_OS_POSIX +# define HAM_OS_POSIX 1 +# if defined(__LP64__) || defined(__LP64) || __WORDSIZE == 64 +# undef HAM_64BIT +# define HAM_64BIT 1 +# else +# undef HAM_32BIT +# define HAM_32BIT 1 +# endif +#endif + +#if defined(HAM_OS_POSIX) && defined(HAM_OS_WIN32) +# error "Unknown arch - neither HAM_OS_POSIX nor HAM_OS_WIN32 defined" +#endif + +/* + * improve memory debugging on WIN32 by using crtdbg.h (only MSVC + * compiler and debug builds!) + * + * make sure crtdbg.h is loaded before malloc.h! + */ +#if defined(_MSC_VER) && defined(HAM_OS_WIN32) +# if (defined(WIN32) || defined(__WIN32)) && !defined(UNDER_CE) +# if defined(DEBUG) || defined(_DEBUG) +# ifndef _CRTDBG_MAP_ALLOC +# define _CRTDBG_MAP_ALLOC 1 +# endif +# endif +# include <crtdbg.h> +# include <malloc.h> +# endif +#endif + +/* + * Create the EXPORT macro for Microsoft Visual C++ + */ +#ifndef HAM_EXPORT +# ifdef _MSC_VER +# define HAM_EXPORT __declspec(dllexport) +# else +# define HAM_EXPORT extern +# endif +#endif + +/* + * The default calling convention is cdecl + */ +#ifndef HAM_CALLCONV +# define HAM_CALLCONV +#endif + +/* + * Common typedefs. Since stdint.h is not available on older versions of + * Microsoft Visual Studio, they get declared here. + * http://msinttypes.googlecode.com/svn/trunk/stdint.h + */ +#if _MSC_VER +# include <ham/msstdint.h> +#else +# include <stdint.h> +#endif + +/* Deprecated typedefs; used prior to 2.1.9. Please do not use them! */ +typedef int64_t ham_s64_t; +typedef uint64_t ham_u64_t; +typedef int32_t ham_s32_t; +typedef uint32_t ham_u32_t; +typedef int16_t ham_s16_t; +typedef uint16_t ham_u16_t; +typedef int8_t ham_s8_t; +typedef uint8_t ham_u8_t; + +/* + * Undefine macros to avoid macro redefinitions + */ +#undef HAM_INVALID_FD +#undef HAM_FALSE +#undef HAM_TRUE + +/** + * a boolean type + */ +typedef int ham_bool_t; +#define HAM_FALSE 0 +#define HAM_TRUE (!HAM_FALSE) + +/** + * typedef for error- and status-code + */ +typedef int ham_status_t; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HAM_TYPES_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/0root/root.h b/plugins/Dbx_kv/src/hamsterdb/src/0root/root.h new file mode 100644 index 0000000000..38e003b7c7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/0root/root.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The root of all evil. This header file must be included *before all others*! + * + * @thread_safe: yes + * @exception_safe: nothrow + */ + +#ifndef HAM_ROOT_H +#define HAM_ROOT_H + +//#define HAM_ENABLE_HELGRIND 1 + +// some feature macros in config.h must be set *before* inclusion +// of any system headers to have the desired effect. +// assume sane default values if there is no config.h. +#ifdef HAVE_CONFIG_H +# include "../config.h" +#else +# define HAVE_MMAP 1 +# define HAVE_UNMMAP 1 +# define HAVE_PREAD 1 +# define HAVE_PWRITE 1 +#endif + +#include "ham/types.h" + +// check for a valid build +#if (!defined(HAM_DEBUG)) +# if (defined(_DEBUG) || defined(DEBUG)) +# define HAM_DEBUG 1 +# endif +#endif + +// the default cache size is 2 MB +#define HAM_DEFAULT_CACHE_SIZE (2 * 1024 * 1024) + +// the default page size is 16 kb +#define HAM_DEFAULT_PAGE_SIZE (16 * 1024) + +// use tcmalloc? +#if HAVE_GOOGLE_TCMALLOC_H == 1 +# if HAVE_LIBTCMALLOC_MINIMAL == 1 +# define HAM_USE_TCMALLOC 1 +# endif +#endif + +#include <stddef.h> +#define OFFSETOF(type, member) offsetof(type, member) + +// helper macros to improve CPU branch prediction +#if defined __GNUC__ +# define likely(x) __builtin_expect ((x), 1) +# define unlikely(x) __builtin_expect ((x), 0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +#endif + +#ifdef WIN32 +// MSVC: disable warning about use of 'this' in base member initializer list +# pragma warning(disable:4355) +# define WIN32_MEAN_AND_LEAN +# include <windows.h> +#endif + +// some compilers define min and max as macros; this leads to errors +// when using std::min and std::max +#ifdef min +# undef min +#endif + +#ifdef max +# undef max +#endif + +// a macro to cast pointers to u64 and vice versa to avoid compiler +// warnings if the sizes of ptr and u64 are not equal +#if defined(HAM_32BIT) && (!defined(_MSC_VER)) +# define U64_TO_PTR(p) (uint8_t *)(int)p +# define PTR_TO_U64(p) (uint64_t)(int)p +#else +# define U64_TO_PTR(p) p +# define PTR_TO_U64(p) p +#endif + +#endif /* HAM_ROOT_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/abi.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/abi.h new file mode 100644 index 0000000000..57c086f24c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/abi.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Returns the demangled name of a class + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_ABI_H +#define HAM_ABI_H + +#include "0root/root.h" + +#ifdef HAVE_GCC_ABI_DEMANGLE +# include <cxxabi.h> +#endif + +#include <string> +#include <stdlib.h> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +template<class T> inline std::string +get_classname(const T& t) +{ +#ifdef HAVE_GCC_ABI_DEMANGLE + int status; + const std::type_info &ti = typeid(t); + char *name = abi::__cxa_demangle(ti.name(), 0, 0, &status); + if (!name) + return (""); + if (status) { + ::free(name); + return (""); + } + std::string s = name; + ::free(name); + return (s); +#else + return (""); +#endif +} + +} // namespace hamsterdb + +#endif /* HAM_ABI_H */ + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/dynamic_array.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/dynamic_array.h new file mode 100644 index 0000000000..8cd8e2c8b7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/dynamic_array.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A class managing a dynamically sized array for arbitrary types + * + * @exception_safe: strong + * @thread_safe: no + */ + +#ifndef HAM_DYNAMIC_ARRAY_H +#define HAM_DYNAMIC_ARRAY_H + +#include "0root/root.h" + +#include <stdlib.h> +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1mem/mem.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* + * The DynamicArray class is a dynamic, resizable array. The internal memory + * is released when the DynamicArray instance is destructed. + * + * Unlike std::vector, the DynamicArray uses libc functions for constructing, + * copying and initializing elements. + */ +template<typename T> +class DynamicArray +{ + public: + typedef T value_t; + typedef T *pointer_t; + + DynamicArray(size_t size = 0) + : m_ptr(0), m_size(0), m_own(true) { + resize(size); + } + + DynamicArray(size_t size, uint8_t fill_byte) + : m_ptr(0), m_size(0), m_own(true) { + resize(size); + if (m_ptr) + ::memset(m_ptr, fill_byte, sizeof(T) * m_size); + } + + ~DynamicArray() { + clear(); + } + + void append(const T *ptr, size_t size) { + size_t old_size = m_size; + T *p = (T *)resize(m_size + size); + ::memcpy(p + old_size, ptr, sizeof(T) * size); + } + + void copy(const T *ptr, size_t size) { + resize(size); + ::memcpy(m_ptr, ptr, sizeof(T) * size); + m_size = size; + } + + void overwrite(uint32_t position, const T *ptr, size_t size) { + ::memcpy(((uint8_t *)m_ptr) + position, ptr, sizeof(T) * size); + } + + T *resize(size_t size) { + if (size > m_size) { + m_ptr = Memory::reallocate<T>(m_ptr, sizeof(T) * size); + m_size = size; + } + return (m_ptr); + } + + T *resize(size_t size, uint8_t fill_byte) { + resize(size); + if (m_ptr) + ::memset(m_ptr, fill_byte, sizeof(T) * size); + return (m_ptr); + } + + size_t get_size() const { + return (m_size); + } + + void set_size(size_t size) { + m_size = size; + } + + T *get_ptr() { + return (m_ptr); + } + + const T *get_ptr() const { + return (m_ptr); + } + + void assign(T *ptr, size_t size) { + clear(); + m_ptr = ptr; + m_size = size; + } + + void clear(bool release_memory = true) { + if (m_own && release_memory) + Memory::release(m_ptr); + m_ptr = 0; + m_size = 0; + } + + bool is_empty() const { + return (m_size == 0); + } + + void disown() { + m_own = false; + } + + private: + // Pointer to the data + T *m_ptr; + + // The size of the array + size_t m_size; + + // True if the destructor should free the pointer + bool m_own; +}; + +/* + * A ByteArray is a DynamicArray for bytes + */ +typedef DynamicArray<uint8_t> ByteArray; + +} // namespace hamsterdb + +#endif // HAM_DYNAMIC_ARRAY_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/error.cc b/plugins/Dbx_kv/src/hamsterdb/src/1base/error.cc new file mode 100644 index 0000000000..c7ebc530bb --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/error.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/util.h" +#include "1globals/globals.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +void (*ham_test_abort)(void); + +static int +dbg_snprintf(char *str, size_t size, const char *format, ...) +{ + int s; + + va_list ap; + va_start(ap, format); + s = util_vsnprintf(str, size, format, ap); + va_end(ap); + + return (s); +} + +void HAM_CALLCONV +default_errhandler(int level, const char *message) +{ +#ifndef HAM_DEBUG + if (level == HAM_DEBUG_LEVEL_DEBUG) + return; +#endif + fprintf(stderr, "%s\n", message); +} + +void +dbg_prepare(int level, const char *file, int line, const char *function, + const char *expr) +{ + Globals::ms_error_level = level; + Globals::ms_error_file = file; + Globals::ms_error_line = line; + Globals::ms_error_expr = expr; + Globals::ms_error_function = function; +} + +void +dbg_log(const char *format, ...) +{ + int s = 0; + char buffer[1024 * 4]; + + va_list ap; + va_start(ap, format); +#ifdef HAM_DEBUG + s = dbg_snprintf(buffer, sizeof(buffer), "%s[%d]: ", + Globals::ms_error_file, Globals::ms_error_line); + util_vsnprintf(buffer + s, sizeof(buffer) - s, format, ap); +#else + if (Globals::ms_error_function) + s = dbg_snprintf(buffer, sizeof(buffer), "%s: ", + Globals::ms_error_function); + util_vsnprintf(buffer + s, sizeof(buffer) - s, format, ap); +#endif + va_end(ap); + + Globals::ms_error_handler(Globals::ms_error_level, buffer); +} + +/* coverity[+kill] */ +void +dbg_verify_failed(int level, const char *file, int line, const char *function, + const char *expr) +{ + char buffer[1024 * 4]; + + if (!expr) + expr = "(none)"; + + dbg_snprintf(buffer, sizeof(buffer), + "ASSERT FAILED in file %s, line %d:\n\t\"%s\"\n", + file, line, expr); + buffer[sizeof(buffer) - 1] = '\0'; + + Globals::ms_error_handler(Globals::ms_error_level, buffer); + + if (ham_test_abort) + ham_test_abort(); + else + abort(); +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/error.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/error.h new file mode 100644 index 0000000000..f02a8a8c24 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/error.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Error handling routines, assert macros, logging facilities + * + * @exception_safe: nothrow + * @thread_safe: no (b/c of the logging macros) + */ + +#ifndef HAM_ERROR_H +#define HAM_ERROR_H + +#include "0root/root.h" + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// A generic exception for storing a status code +// +struct Exception +{ + Exception(ham_status_t st) + : code(st) { + } + + ham_status_t code; +}; + +// the default error handler +void HAM_CALLCONV +default_errhandler(int level, const char *message); + +extern void +dbg_prepare(int level, const char *file, int line, const char *function, + const char *expr); + +extern void +dbg_log(const char *format, ...); + +#define CLANG_ANALYZER_NORETURN +#if __clang__ +# if __has_feature(attribute_analyzer_noreturn) +# undef CLANG_ANALYZER_NORETURN +# define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn)) +# endif +#endif + +// causes the actual abort() +extern void +dbg_verify_failed(int level, const char *file, int line, + const char *function, const char *expr) CLANG_ANALYZER_NORETURN; + +// a hook for unittests; will be triggered when an assert fails +extern void (*ham_test_abort)(); + +// if your compiler does not support __FUNCTION__, you can define it here: +// #define __FUNCTION__ 0 + +/* + * in debug mode we write trace()-messages to stderr, and assert() + * is enabled. + * + * not every preprocessor supports ellipsis as macro-arguments - + * therefore we have to use brackets, so preprocessors treat multiple + * arguments like a single argument. and we need to lock the output, + * otherwise we are not thread-safe. this is super-ugly. + */ +#ifdef HAM_DEBUG +# define ham_assert(e) while (!(e)) { \ + hamsterdb::dbg_verify_failed(HAM_DEBUG_LEVEL_FATAL, __FILE__, \ + __LINE__, __FUNCTION__, #e); \ + break; \ + } +#else /* !HAM_DEBUG */ +# define ham_assert(e) (void)0 +#endif /* HAM_DEBUG */ + +// ham_log() and ham_verify() are available in every build +#define ham_trace(f) do { \ + hamsterdb::dbg_prepare(HAM_DEBUG_LEVEL_DEBUG, __FILE__, \ + __LINE__, __FUNCTION__, 0); \ + hamsterdb::dbg_log f; \ + } while (0) + +#define ham_log(f) do { \ + hamsterdb::dbg_prepare(HAM_DEBUG_LEVEL_NORMAL, __FILE__, \ + __LINE__, __FUNCTION__, 0); \ + hamsterdb::dbg_log f; \ + } while (0) + +#define ham_verify(e) if (!(e)) { \ + hamsterdb::dbg_verify_failed(HAM_DEBUG_LEVEL_FATAL, __FILE__, \ + __LINE__, __FUNCTION__, #e); \ + } + +} // namespace hamsterdb + +#endif /* HAM_ERROR_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/mutex.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/mutex.h new file mode 100644 index 0000000000..0e09ae046c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/mutex.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A operating-system dependent mutex + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_MUTEX_H +#define HAM_MUTEX_H + +#include "0root/root.h" + +#define BOOST_ALL_NO_LIB // disable MSVC auto-linking +#include <boost/version.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/tss.hpp> +#include <boost/thread/condition.hpp> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +typedef boost::mutex::scoped_lock ScopedLock; +typedef boost::thread Thread; +typedef boost::condition Condition; +typedef boost::mutex Mutex; +typedef boost::recursive_mutex RecursiveMutex; + +} // namespace hamsterdb + +#endif /* HAM_MUTEX_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/packstart.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/packstart.h new file mode 100644 index 0000000000..3a6b1981a7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/packstart.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Macros for packing structures; should work with most compilers. + * + * Example usage: + * + * #include "packstart.h" + * + * typedef HAM_PACK_0 struct HAM_PACK_1 foo { + * int bar; + * } HAM_PACK_2 foo_t; + * + * #include "packstop.h" + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +/* This class does NOT include root.h! */ + +#ifdef __GNUC__ +# if (((__GNUC__==2) && (__GNUC_MINOR__>=7)) || (__GNUC__>2)) +# define HAM_PACK_2 __attribute__ ((packed)) +# define _NEWGNUC_ +# endif +#endif + +#ifdef __WATCOMC__ +# define HAM_PACK_0 _Packed +#endif + +#if (defined(_MSC_VER) && (_MSC_VER >= 900)) || defined(__BORLANDC__) +# define _NEWMSC_ +#endif +#if !defined(_NEWGNUC_) && !defined(__WATCOMC__) && !defined(_NEWMSC_) +# pragma pack(1) +#endif +#ifdef _NEWMSC_ +# pragma pack(push, 1) +# define HAM_PACK_2 __declspec(align(1)) +#endif + +#if defined(_NEWMSC_) && !defined(_WIN32_WCE) +# pragma pack(push, 1) +# define HAM_PACK_2 __declspec(align(1)) +#endif + +#ifndef HAM_PACK_0 +# define HAM_PACK_0 +#endif + +#ifndef HAM_PACK_1 +# define HAM_PACK_1 +#endif + +#ifndef HAM_PACK_2 +# define HAM_PACK_2 +#endif + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/packstop.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/packstop.h new file mode 100644 index 0000000000..a32566f4f9 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/packstop.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Macros for packing structures; should work with most compilers. + * See packstart.h for a usage example. + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +/* This class does NOT include root.h! */ + +#if !defined(_NEWGNUC_) && !defined(__WATCOMC__) && !defined(_NEWMSC_) +# pragma pack() +#endif +#ifdef _NEWMSC_ +# pragma pack(pop) +#endif +#if defined(_NEWMSC_) && !defined(_WIN32_WCE) +# pragma pack(pop) +#endif + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/pickle.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/pickle.h new file mode 100644 index 0000000000..8927e08910 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/pickle.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Class for pickling/unpickling data to a buffer + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_PICKLE_H +#define HAM_PICKLE_H + +#include "0root/root.h" + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Pickle { + /* encodes a uint64 number and stores it in |p|; returns the number of + * bytes used */ + static size_t encode_u64(uint8_t *p, uint64_t n) { + if (n <= 0xf) { + *p = (uint8_t)n; + return (1); + } + if (n <= 0xff) { + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (2); + } + if (n <= 0xfff) { + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (3); + } + if (n <= 0xffff) { + *(p + 3) = (n & 0xf000) >> 12; + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (4); + } + if (n <= 0xfffff) { + *(p + 4) = (n & 0xf0000) >> 16; + *(p + 3) = (n & 0xf000) >> 12; + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (5); + } + if (n <= 0xffffff) { + *(p + 5) = (n & 0xf00000) >> 24; + *(p + 4) = (n & 0xf0000) >> 16; + *(p + 3) = (n & 0xf000) >> 12; + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (6); + } + if (n <= 0xfffffff) { + *(p + 6) = (n & 0xf000000) >> 32; + *(p + 5) = (n & 0xf00000) >> 24; + *(p + 4) = (n & 0xf0000) >> 16; + *(p + 3) = (n & 0xf000) >> 12; + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (7); + } + *(p + 7) = (n & 0xf0000000) >> 36; + *(p + 6) = (n & 0xf000000) >> 32; + *(p + 5) = (n & 0xf00000) >> 24; + *(p + 4) = (n & 0xf0000) >> 16; + *(p + 3) = (n & 0xf000) >> 12; + *(p + 2) = (n & 0xf00) >> 8; + *(p + 1) = (n & 0xf0) >> 4; + *(p + 0) = n & 0xf; + return (8); + } + + /* decodes and returns a pickled number of |len| bytes */ + static uint64_t decode_u64(size_t len, uint8_t *p) { + uint64_t ret = 0; + + for (size_t i = 0; i < len - 1; i++) { + ret += *(p + (len - i - 1)); + ret <<= 4; + } + + // last assignment is without *= 10 + return (ret + *p); + } +}; + +} // namespace hamsterdb + +#endif // HAM_PICKLE_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/scoped_ptr.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/scoped_ptr.h new file mode 100644 index 0000000000..b920059aad --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/scoped_ptr.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A (stupid) smart pointer + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_SCOPED_PTR_H +#define HAM_SCOPED_PTR_H + +#include "0root/root.h" + +#define BOOST_ALL_NO_LIB // disable MSVC auto-linking +#include <boost/scoped_ptr.hpp> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +template <typename T> +struct ScopedPtr : public boost::scoped_ptr<T> +{ + ScopedPtr() + : boost::scoped_ptr<T>() { + } + + ScopedPtr(T *t) + : boost::scoped_ptr<T>(t) { + } +}; + +} // namespace hamsterdb + +#endif /* HAM_SCOPED_PTR_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/spinlock.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/spinlock.h new file mode 100644 index 0000000000..e9d917212c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/spinlock.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A fast spinlock, taken from the boost documentation + * http://www.boost.org/doc/libs/1_57_0/doc/html/atomic/usage_examples.html + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_SPINLOCK_H +#define HAM_SPINLOCK_H + +#include "0root/root.h" + +#include <stdio.h> +#ifndef HAM_OS_WIN32 +# include <sched.h> +# include <unistd.h> +#endif +#include <boost/atomic.hpp> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/mutex.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +#ifdef HAM_ENABLE_HELGRIND +typedef Mutex Spinlock; +#else + +class Spinlock { + typedef enum { + kLocked, + kUnlocked, + kSpinThreshold = 10 + } LockState; + + public: + Spinlock() + : m_state(kUnlocked) { + } + + // Need user-defined copy constructor because boost::atomic<> is not + // copyable + Spinlock(const Spinlock &other) + : m_state(other.m_state.load()) { + } + + void lock() { + int k = 0; + while (m_state.exchange(kLocked, boost::memory_order_acquire) == kLocked) + spin(++k); + } + + void unlock() { + m_state.store(kUnlocked, boost::memory_order_release); + } + + bool try_lock() { + return (m_state.exchange(kLocked, boost::memory_order_acquire) + != kLocked); + } + + static void spin(int loop) { + if (loop < kSpinThreshold) { +#ifdef HAM_OS_WIN32 + ::Sleep(0); +#elif HAVE_SCHED_YIELD + ::sched_yield(); +#else + ham_assert(!"Please implement me"); +#endif + } + else { +#ifdef HAM_OS_WIN32 + ::Sleep(25); +#elif HAVE_USLEEP + ::usleep(25); +#else + ham_assert(!"Please implement me"); +#endif + } + } + + private: + boost::atomic<LockState> m_state; +}; +#endif // HAM_ENABLE_HELGRIND + +class ScopedSpinlock { + public: + ScopedSpinlock(Spinlock &lock) + : m_spinlock(lock) { + m_spinlock.lock(); + } + + ~ScopedSpinlock() { + m_spinlock.unlock(); + } + + private: + Spinlock &m_spinlock; +}; + +} // namespace hamsterdb + +#endif /* HAM_SPINLOCK_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/util.cc b/plugins/Dbx_kv/src/hamsterdb/src/1base/util.cc new file mode 100644 index 0000000000..828fb3ec9d --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/util.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Always verify that a file of level N does not include headers > N! +#include "1base/util.h" + +namespace hamsterdb { + +int +util_vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ +#if defined(HAM_OS_POSIX) + return vsnprintf(str, size, format, ap); +#elif defined(HAM_OS_WIN32) + return _vsnprintf(str, size, format, ap); +#else + (void)size; + return (vsprintf(str, format, ap)); +#endif +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1base/util.h b/plugins/Dbx_kv/src/hamsterdb/src/1base/util.h new file mode 100644 index 0000000000..4e7857bd34 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1base/util.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Misc. utility classes and functions + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_UTIL_H +#define HAM_UTIL_H + +#include "0root/root.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// vsnprintf replacement/wrapper +// +// uses vsprintf on platforms which do not define vsnprintf +// +extern int +util_vsnprintf(char *str, size_t size, const char *format, va_list ap); + +// +// snprintf replacement/wrapper +// +// uses sprintf on platforms which do not define snprintf +// +#ifndef HAM_OS_POSIX +# define util_snprintf _snprintf +#else +# define util_snprintf snprintf +#endif + +} // namespace hamsterdb + +#endif // HAM_UTIL_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.cc b/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.cc new file mode 100644 index 0000000000..9f343c5ed6 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1errorinducer/errorinducer.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +ErrorInducer ErrorInducer::ms_instance; +bool ErrorInducer::ms_is_active = false; + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.h b/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.h new file mode 100644 index 0000000000..4a7b2107af --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1errorinducer/errorinducer.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Facility to simulate errors + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_ERRORINDUCER_H +#define HAM_ERRORINDUCER_H + +#include "0root/root.h" + +#include <string.h> + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +// a macro to invoke errors +#define HAM_INDUCE_ERROR(id) \ + while (ErrorInducer::is_active()) { \ + ham_status_t st = ErrorInducer::get_instance()->induce(id); \ + if (st) \ + throw Exception(st); \ + break; \ + } + +namespace hamsterdb { + +class ErrorInducer { + struct State { + State() + : loops(0), error(HAM_INTERNAL_ERROR) { + } + + int loops; + ham_status_t error; + }; + + public: + enum Action { + // simulates a failure in Changeset::flush + kChangesetFlush, + + // simulates a hang in hamserver-connect + kServerConnect, + + kMaxActions + }; + + // Activates or deactivates the error inducer + static void activate(bool active) { + ms_is_active = active; + } + + // Returns true if the error inducer is active + static bool is_active() { + return (ms_is_active); + } + + // Returns the singleton instance + static ErrorInducer *get_instance() { + return (&ms_instance); + } + + ErrorInducer() { + memset(&m_state[0], 0, sizeof(m_state)); + } + + void add(Action action, int loops, + ham_status_t error = HAM_INTERNAL_ERROR) { + m_state[action].loops = loops; + m_state[action].error = error; + } + + ham_status_t induce(Action action) { + ham_assert(m_state[action].loops >= 0); + if (m_state[action].loops > 0 && --m_state[action].loops == 0) + return (m_state[action].error); + return (0); + } + + private: + State m_state[kMaxActions]; + + // The singleton instance + static ErrorInducer ms_instance; + + // Is the ErrorInducer active? + static bool ms_is_active; +}; + +} // namespace hamsterdb + +#endif /* HAM_ERRORINDUCER_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.cc b/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.cc new file mode 100644 index 0000000000..9f5d184c55 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.cc @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +uint64_t Globals::ms_extended_keys; + +uint64_t Globals::ms_extended_duptables; + +uint32_t Globals::ms_extended_threshold; + +uint32_t Globals::ms_duplicate_threshold; + +int Globals::ms_linear_threshold; + +int Globals::ms_error_level; + +const char *Globals::ms_error_file; + +int Globals::ms_error_line; + +const char *Globals::ms_error_expr; + +const char *Globals::ms_error_function; + +// the default error handler +void HAM_CALLCONV default_errhandler(int level, const char *message); + +ham_errhandler_fun Globals::ms_error_handler = default_errhandler; + +uint64_t Globals::ms_bytes_before_compression; + +uint64_t Globals::ms_bytes_after_compression; + +bool Globals::ms_is_simd_enabled = true; + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.h b/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.h new file mode 100644 index 0000000000..efe3449e93 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1globals/globals.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Global variables; used for tests and metrics + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_GLOBALS_H +#define HAM_GLOBALS_H + +#include "0root/root.h" + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Globals { + // for counting extended keys + static uint64_t ms_extended_keys; + + // for counting extended duplicate tables + static uint64_t ms_extended_duptables; + + // Move every key > threshold to a blob. For testing purposes. + // TODO currently gets assigned at runtime + static uint32_t ms_extended_threshold; + + // Create duplicate table if amount of duplicates > threshold. For testing + // purposes. + // TODO currently gets assigned at runtime + static uint32_t ms_duplicate_threshold; + + // linear search threshold for the PAX layout + static int ms_linear_threshold; + + // used in error.h/error.cc + static int ms_error_level; + + // used in error.h/error.cc + static const char *ms_error_file; + + // used in error.h/error.cc + static int ms_error_line; + + // used in error.h/error.cc + static const char *ms_error_expr; + + // used in error.h/error.cc + static const char *ms_error_function; + + // used in error.h/error.cc + static ham_errhandler_fun ms_error_handler; + + // PRO: Tracking key bytes before compression + static uint64_t ms_bytes_before_compression; + + // PRO: Tracking key bytes after compression + static uint64_t ms_bytes_after_compression; + + // PRO: enable/disable SIMD + static bool ms_is_simd_enabled; +}; + +} // namespace hamsterdb + +#endif /* HAM_GLOBALS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.cc b/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.cc new file mode 100644 index 0000000000..58a00b87c3 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.cc @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#ifdef HAM_USE_TCMALLOC +# include <google/tcmalloc.h> +# include <google/malloc_extension.h> +#endif +#include <stdlib.h> + +#include "ham/hamsterdb_int.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/file.h" +#include "1mem/mem.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +uint64_t Memory::ms_peak_memory; +uint64_t Memory::ms_total_allocations; +uint64_t Memory::ms_current_allocations; + +void +Memory::get_global_metrics(ham_env_metrics_t *metrics) +{ +#ifdef HAM_USE_TCMALLOC + size_t value = 0; + MallocExtension::instance()->GetNumericProperty( + "generic.current_allocated_bytes", &value); + metrics->mem_current_usage = value; + if (ms_peak_memory < value) + ms_peak_memory = metrics->mem_peak_usage = value; + MallocExtension::instance()->GetNumericProperty( + "generic.heap_size", &value); + metrics->mem_heap_size = value; +#endif + + metrics->mem_total_allocations = ms_total_allocations; + metrics->mem_current_allocations = ms_current_allocations; +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.h b/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.h new file mode 100644 index 0000000000..13f79b618c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1mem/mem.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Memory handling + * + * @exception_safe: nothrow + * @thread_safe: no (b/c of metrics) + */ + +#ifndef HAM_MEM_H +#define HAM_MEM_H + +#include "0root/root.h" + +#include <new> +#include <stdlib.h> +#ifdef HAM_USE_TCMALLOC +# include <google/tcmalloc.h> +#endif + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +struct ham_env_metrics_t; + +namespace hamsterdb { + +/* + * The static Memory class provides memory management functions in a common + * c++ namespace. The functions can allocate, reallocate and free memory + * while tracking usage statistics. + * + * If tcmalloc is used then additional metrics will be available. + * + * This class only has static members and methods. It does not have a + * constructor. + */ +class Memory { + public: + // allocates |size| bytes, casted into type |T *|; + // returns null if out of memory. + // usage: + // + // char *p = Memory::allocate<char>(1024); + // + template<typename T> + static T *allocate(size_t size) { + ms_total_allocations++; + ms_current_allocations++; +#ifdef HAM_USE_TCMALLOC + T *t = (T *)::tc_malloc(size); +#else + T *t = (T *)::malloc(size); +#endif + if (!t) + throw Exception(HAM_OUT_OF_MEMORY); + return (t); + } + + // allocates |size| bytes; returns null if out of memory. initializes + // the allocated memory with zeroes. + // usage: + // + // const char *p = Memory::callocate<const char>(50); + // + template<typename T> + static T *callocate(size_t size) { + ms_total_allocations++; + ms_current_allocations++; + +#ifdef HAM_USE_TCMALLOC + T *t = (T *)::tc_calloc(1, size); +#else + T *t = (T *)::calloc(1, size); +#endif + if (!t) + throw Exception(HAM_OUT_OF_MEMORY); + return (t); + } + + // re-allocates |ptr| for |size| bytes; returns null if out of memory. + // |ptr| can be null on first use. + // usage: + // + // p = Memory::reallocate<char>(p, 100); + // + template<typename T> + static T *reallocate(T *ptr, size_t size) { + if (ptr == 0) { + ms_total_allocations++; + ms_current_allocations++; + } +#ifdef HAM_USE_TCMALLOC + T *t = (T *)::tc_realloc(ptr, size); +#else + T *t = (T *)::realloc(ptr, size); +#endif + if (!t) + throw Exception(HAM_OUT_OF_MEMORY); + return (t); + } + + // releases a memory block; can deal with NULL pointers. + static void release(void *ptr) { + if (ptr) { + ms_current_allocations--; +#ifdef HAM_USE_TCMALLOC + ::tc_free(ptr); +#else + ::free(ptr); +#endif + } + } + + // updates and returns the collected metrics + static void get_global_metrics(ham_env_metrics_t *metrics); + + private: + // peak memory usage + static uint64_t ms_peak_memory; + + // total memory allocations + static uint64_t ms_total_allocations; + + // currently active allocations + static uint64_t ms_current_allocations; +}; + +} // namespace hamsterdb + +#endif /* HAM_MEM_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/file.h b/plugins/Dbx_kv/src/hamsterdb/src/1os/file.h new file mode 100644 index 0000000000..df9049c6de --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/file.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A simple wrapper around a file handle. Throws exceptions in + * case of errors. Moves the file handle when copied. + * + * @exception_safe: strong + * @thread_safe: unknown + */ + +#ifndef HAM_FILE_H +#define HAM_FILE_H + +#include "0root/root.h" + +#include <stdio.h> +#include <limits.h> + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/os.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class File +{ + public: + enum { +#ifdef HAM_OS_POSIX + kSeekSet = SEEK_SET, + kSeekEnd = SEEK_END, + kSeekCur = SEEK_CUR, + kMaxPath = PATH_MAX +#else + kSeekSet = FILE_BEGIN, + kSeekEnd = FILE_END, + kSeekCur = FILE_CURRENT, + kMaxPath = MAX_PATH +#endif + }; + + // Constructor: creates an empty File handle + File() + : m_fd(HAM_INVALID_FD), m_mmaph(HAM_INVALID_FD), m_posix_advice(0) { + } + + // Copy constructor: moves ownership of the file handle + File(File &other) + : m_fd(other.m_fd), m_mmaph(other.m_mmaph), + m_posix_advice(other.m_posix_advice) { + other.m_fd = HAM_INVALID_FD; + other.m_mmaph = HAM_INVALID_FD; + } + + // Destructor: closes the file + ~File() { + close(); + } + + // Assignment operator: moves ownership of the file handle + File &operator=(File &other) { + m_fd = other.m_fd; + other.m_fd = HAM_INVALID_FD; + return (*this); + } + + // Creates a new file + void create(const char *filename, uint32_t mode); + + // Opens an existing file + void open(const char *filename, bool read_only); + + // Returns true if the file is open + bool is_open() const { + return (m_fd != HAM_INVALID_FD); + } + + // Flushes a file + void flush(); + + // Sets the parameter for posix_fadvise() + void set_posix_advice(int parameter); + + // Maps a file in memory + // + // mmap is called with MAP_PRIVATE - the allocated buffer + // is just a copy of the file; writing to the buffer will not alter + // the file itself. + void mmap(uint64_t position, size_t size, bool readonly, + uint8_t **buffer); + + // Unmaps a buffer + void munmap(void *buffer, size_t size); + + // Positional read from a file + void pread(uint64_t addr, void *buffer, size_t len); + + // Positional write to a file + void pwrite(uint64_t addr, const void *buffer, size_t len); + + // Write data to a file; uses the current file position + void write(const void *buffer, size_t len); + + // Get the page allocation granularity of the operating system + static size_t get_granularity(); + + // Seek position in a file + void seek(uint64_t offset, int whence); + + // Tell the position in a file + uint64_t tell(); + + // Returns the size of the file + uint64_t get_file_size(); + + // Truncate/resize the file + void truncate(uint64_t newsize); + + // Closes the file descriptor + void close(); + + private: + // The file handle + ham_fd_t m_fd; + + // The mmap handle - required for Win32 + ham_fd_t m_mmaph; + + // Parameter for posix_fadvise() + int m_posix_advice; +}; + +} // namespace hamsterdb + +#endif /* HAM_FILE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/os.cc b/plugins/Dbx_kv/src/hamsterdb/src/1os/os.cc new file mode 100644 index 0000000000..8f8c0c991c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/os.cc @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "1os/os.h" + +namespace hamsterdb { + +int +os_get_simd_lane_width() +{ + // only supported in hamsterdb pro + return (0); +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/os.h b/plugins/Dbx_kv/src/hamsterdb/src/1os/os.h new file mode 100644 index 0000000000..dd2f52a4dc --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/os.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Abstraction layer for operating system functions + * + * @exception_safe: basic // for socket + * @exception_safe: strong // for file + * @thread_safe: unknown + */ + +#ifndef HAM_OS_H +#define HAM_OS_H + +#include "0root/root.h" + +#include <stdio.h> +#include <limits.h> + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* + * typedefs for posix + */ +#ifdef HAM_OS_POSIX +typedef int ham_fd_t; +typedef int ham_socket_t; +# define HAM_INVALID_FD (-1) +#endif + +/* + * typedefs for Windows 32- and 64-bit + */ +#ifdef HAM_OS_WIN32 +# ifdef CYGWIN +typedef int ham_fd_t; +typedef int ham_socket_t; +# else +typedef HANDLE ham_fd_t; +typedef SOCKET ham_socket_t; +# endif +# define HAM_INVALID_FD (0) +#endif + +// Returns the number of 32bit integers that the CPU can process in +// parallel (the SIMD lane width) +extern int +os_get_simd_lane_width(); + +} // namespace hamsterdb + +#endif /* HAM_OS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/os_posix.cc b/plugins/Dbx_kv/src/hamsterdb/src/1os/os_posix.cc new file mode 100644 index 0000000000..135899e7ea --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/os_posix.cc @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE 1 // for O_LARGEFILE +#define _FILE_OFFSET_BITS 64 + +#include "0root/root.h" + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#if HAVE_MMAP +# include <sys/mman.h> +#endif +#if HAVE_WRITEV +# include <sys/uio.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> +#include <unistd.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1os/file.h" +#include "1os/socket.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +#if 0 +# define os_log(x) ham_log(x) +#else +# define os_log(x) +#endif + +static void +lock_exclusive(int fd, bool lock) +{ +#ifdef HAM_SOLARIS + // SunOS 5.9 doesn't have LOCK_* unless i include /usr/ucbinclude; but then, + // mmap behaves strangely (the first write-access to the mmapped buffer + // leads to a segmentation fault). + // + // Tell me if this troubles you/if you have suggestions for fixes. +#else + int flags; + + if (lock) + flags = LOCK_EX | LOCK_NB; + else + flags = LOCK_UN; + + if (0 != flock(fd, flags)) { + ham_log(("flock failed with status %u (%s)", errno, strerror(errno))); + // it seems that linux does not only return EWOULDBLOCK, as stated + // in the documentation (flock(2)), but also other errors... + if (errno && lock) + throw Exception(HAM_WOULD_BLOCK); + throw Exception(HAM_IO_ERROR); + } +#endif +} + +static void +enable_largefile(int fd) +{ + // not available on cygwin... +#ifdef HAVE_O_LARGEFILE + int oflag = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, oflag | O_LARGEFILE); +#endif +} + +static void +os_read(ham_fd_t fd, uint8_t *buffer, size_t len) +{ + os_log(("os_read: fd=%d, size=%lld", fd, len)); + + int r; + size_t total = 0; + + while (total < len) { + r = read(fd, &buffer[total], len - total); + if (r < 0) { + ham_log(("os_read failed with status %u (%s)", errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + if (r == 0) + break; + total += r; + } + + if (total != len) { + ham_log(("os_read() failed with short read (%s)", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +} + +static void +os_write(ham_fd_t fd, const void *buffer, size_t len) +{ + int w; + size_t total = 0; + const char *p = (const char *)buffer; + + while (total < len) { + w = ::write(fd, p + total, len - total); + if (w < 0) { + ham_log(("os_write failed with status %u (%s)", errno, + strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + if (w == 0) + break; + total += w; + } + + if (total != len) { + ham_log(("os_write() failed with short read (%s)", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +} + +size_t +File::get_granularity() +{ + return ((size_t)sysconf(_SC_PAGE_SIZE)); +} + +void +File::set_posix_advice(int advice) +{ + m_posix_advice = advice; + ham_assert(m_fd != HAM_INVALID_FD); + +#if HAVE_POSIX_FADVISE + if (m_posix_advice == HAM_POSIX_FADVICE_RANDOM) { + int r = ::posix_fadvise(m_fd, 0, 0, POSIX_FADV_RANDOM); + if (r != 0) { + ham_log(("posix_fadvise failed with status %d (%s)", + errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + } +#endif +} + +void +File::mmap(uint64_t position, size_t size, bool readonly, uint8_t **buffer) +{ + os_log(("File::mmap: fd=%d, position=%lld, size=%lld", m_fd, position, size)); + + int prot = PROT_READ; + if (!readonly) + prot |= PROT_WRITE; + +#if HAVE_MMAP + *buffer = (uint8_t *)::mmap(0, size, prot, MAP_PRIVATE, m_fd, position); + if (*buffer == (void *)-1) { + *buffer = 0; + ham_log(("mmap failed with status %d (%s)", errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +#else + throw Exception(HAM_NOT_IMPLEMENTED); +#endif + +#if HAVE_MADVISE + if (m_posix_advice == HAM_POSIX_FADVICE_RANDOM) { + int r = ::madvise(*buffer, size, MADV_RANDOM); + if (r != 0) { + ham_log(("madvise failed with status %d (%s)", errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + } +#endif +} + +void +File::munmap(void *buffer, size_t size) +{ + os_log(("File::munmap: size=%lld", size)); + +#if HAVE_MUNMAP + int r = ::munmap(buffer, size); + if (r) { + ham_log(("munmap failed with status %d (%s)", errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +#else + throw Exception(HAM_NOT_IMPLEMENTED); +#endif +} + +void +File::pread(uint64_t addr, void *buffer, size_t len) +{ +#if HAVE_PREAD + os_log(("File::pread: fd=%d, address=%lld, size=%lld", m_fd, addr, + len)); + + int r; + size_t total = 0; + + while (total < len) { + r = ::pread(m_fd, (uint8_t *)buffer + total, len - total, + addr + total); + if (r < 0) { + ham_log(("File::pread failed with status %u (%s)", errno, + strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + if (r == 0) + break; + total += r; + } + + if (total != len) { + ham_log(("File::pread() failed with short read (%s)", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +#else + File::seek(addr, kSeekSet); + os_read(m_fd, (uint8_t *)buffer, len); +#endif +} + +void +File::pwrite(uint64_t addr, const void *buffer, size_t len) +{ + os_log(("File::pwrite: fd=%d, address=%lld, size=%lld", m_fd, addr, len)); + +#if HAVE_PWRITE + ssize_t s; + size_t total = 0; + + while (total < len) { + s = ::pwrite(m_fd, buffer, len, addr + total); + if (s < 0) { + ham_log(("pwrite() failed with status %u (%s)", errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + if (s == 0) + break; + total += s; + } + + if (total != len) { + ham_log(("pwrite() failed with short read (%s)", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +#else + seek(addr, kSeekSet); + write(buffer, len); +#endif +} + +void +File::write(const void *buffer, size_t len) +{ + os_log(("File::write: fd=%d, size=%lld", m_fd, len)); + os_write(m_fd, buffer, len); +} + +void +File::seek(uint64_t offset, int whence) +{ + os_log(("File::seek: fd=%d, offset=%lld, whence=%d", m_fd, offset, whence)); + if (lseek(m_fd, offset, whence) < 0) + throw Exception(HAM_IO_ERROR); +} + +uint64_t +File::tell() +{ + uint64_t offset = lseek(m_fd, 0, SEEK_CUR); + os_log(("File::tell: fd=%d, offset=%lld", m_fd, offset)); + if (offset == (uint64_t) - 1) + throw Exception(HAM_IO_ERROR); + return (offset); +} + +uint64_t +File::get_file_size() +{ + seek(0, kSeekEnd); + uint64_t size = tell(); + os_log(("File::get_file_size: fd=%d, size=%lld", m_fd, size)); + return (size); +} + +void +File::truncate(uint64_t newsize) +{ + os_log(("File::truncate: fd=%d, size=%lld", m_fd, newsize)); + if (ftruncate(m_fd, newsize)) + throw Exception(HAM_IO_ERROR); +} + +void +File::create(const char *filename, uint32_t mode) +{ + int osflags = O_CREAT | O_RDWR | O_TRUNC; +#if HAVE_O_NOATIME + osflags |= O_NOATIME; +#endif + + ham_fd_t fd = ::open(filename, osflags, mode ? mode : 0644); + if (fd < 0) { + ham_log(("creating file %s failed with status %u (%s)", filename, + errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + + /* lock the file - this is default behaviour since 1.1.0 */ + lock_exclusive(fd, true); + + /* enable O_LARGEFILE support */ + enable_largefile(fd); + + m_fd = fd; +} + +void +File::flush() +{ + os_log(("File::flush: fd=%d", m_fd)); + /* unlike fsync(), fdatasync() does not flush the metadata unless + * it's really required. it's therefore a lot faster. */ +#if HAVE_FDATASYNC && !__APPLE__ + if (fdatasync(m_fd) == -1) { +#else + if (fsync(m_fd) == -1) { +#endif + ham_log(("fdatasync failed with status %u (%s)", + errno, strerror(errno))); + throw Exception(HAM_IO_ERROR); + } +} + +void +File::open(const char *filename, bool read_only) +{ + int osflags = 0; + + if (read_only) + osflags |= O_RDONLY; + else + osflags |= O_RDWR; +#if HAVE_O_NOATIME + osflags |= O_NOATIME; +#endif + + ham_fd_t fd = ::open(filename, osflags); + if (fd < 0) { + ham_log(("opening file %s failed with status %u (%s)", filename, + errno, strerror(errno))); + throw Exception(errno == ENOENT ? HAM_FILE_NOT_FOUND : HAM_IO_ERROR); + } + + /* lock the file - this is default behaviour since 1.1.0 */ + lock_exclusive(fd, true); + + /* enable O_LARGEFILE support */ + enable_largefile(fd); + + m_fd = fd; +} + +void +File::close() +{ + if (m_fd != HAM_INVALID_FD) { + // on posix, we most likely don't want to close descriptors 0 and 1 + ham_assert(m_fd != 0 && m_fd != 1); + + // unlock the file - this is default behaviour since 1.1.0 + lock_exclusive(m_fd, false); + + // now close the descriptor + if (::close(m_fd) == -1) + throw Exception(HAM_IO_ERROR); + + m_fd = HAM_INVALID_FD; + } +} + +void +Socket::connect(const char *hostname, uint16_t port, uint32_t timeout_sec) +{ + ham_socket_t s = ::socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + ham_log(("failed creating socket: %s", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + + struct hostent *server = ::gethostbyname(hostname); + if (!server) { + ham_log(("unable to resolve hostname %s: %s", hostname, + hstrerror(h_errno))); + ::close(s); + throw Exception(HAM_NETWORK_ERROR); + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length); + addr.sin_port = htons(port); + if (::connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ham_log(("unable to connect to %s:%d: %s", hostname, (int)port, + strerror(errno))); + ::close(s); + throw Exception(HAM_NETWORK_ERROR); + } + + if (timeout_sec) { + struct timeval tv; + tv.tv_sec = timeout_sec; + tv.tv_usec = 0; + if (::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0) { + ham_log(("unable to set socket timeout to %d sec: %s", timeout_sec, + strerror(errno))); + // fall through, this is not critical + } + } + + m_socket = s; +} + +void +Socket::send(const uint8_t *data, size_t len) +{ + os_write(m_socket, data, len); +} + +void +Socket::recv(uint8_t *data, size_t len) +{ + os_read(m_socket, data, len); +} + +void +Socket::close() +{ + if (m_socket != HAM_INVALID_FD) { + if (::close(m_socket) == -1) + throw Exception(HAM_IO_ERROR); + m_socket = HAM_INVALID_FD; + } +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/os_win32.cc b/plugins/Dbx_kv/src/hamsterdb/src/1os/os_win32.cc new file mode 100644 index 0000000000..ac51a4a7b7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/os_win32.cc @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winsock2.h> +#include <windows.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1os/file.h" +#include "1os/socket.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +static const char * +DisplayError(char* buf, uint32_t buflen, DWORD errorcode) +{ + size_t len; + + buf[0] = 0; + FormatMessageA(/* FORMAT_MESSAGE_ALLOCATE_BUFFER | */ + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorcode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)buf, buflen, NULL); + buf[buflen - 1] = 0; + + /* strip trailing whitespace\newlines */ + for (len = strlen(buf); len-- > 0; ) { + if (!isspace(buf[len])) + break; + buf[len] = 0; + } + + return (buf); +} + +/* + * MS says: + * + * Security Alert + * + * Using the MultiByteToWideChar function incorrectly can compromise the + * security of your application. Calling this function can easily cause a + * buffer overrun because the size of the input buffer indicated by + * lpMultiByteStr equals the number of bytes in the string, while the size of + * the output buffer indicated by lpWideCharStr equals the number of WCHAR + * values. + * + * To avoid a buffer overrun, your application must specify a buffer size + * appropriate for the data type the buffer receives. For more information, see + * Security Considerations: International Features. + */ +static void +utf8_string(const char *filename, WCHAR *wfilename, int wlen) +{ + MultiByteToWideChar(CP_ACP, 0, filename, -1, wfilename, wlen); +} + +static int +calc_wlen4str(const char *str) +{ + // Since we call MultiByteToWideChar with an input length of -1, the + // output will include the wchar NUL sentinel as well, so count it + return (int)(strlen(str) + 1); +} + +size_t +File::get_granularity() +{ + SYSTEM_INFO info; + GetSystemInfo(&info); + return ((size_t)info.dwAllocationGranularity); +} + +void +File::set_posix_advice(int advice) +{ + // Only available for posix platforms +} + +void +File::mmap(uint64_t position, size_t size, bool readonly, uint8_t **buffer) +{ + ham_status_t st; + DWORD protect = (readonly ? PAGE_READONLY : PAGE_WRITECOPY); + DWORD access = FILE_MAP_COPY; + LARGE_INTEGER i; + i.QuadPart = position; + + m_mmaph = CreateFileMapping(m_fd, 0, protect, 0, 0, 0); + if (!m_mmaph) { + char buf[256]; + *buffer = 0; + st = (ham_status_t)GetLastError(); + ham_log(("CreateFileMapping failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + *buffer = (uint8_t *)MapViewOfFile(m_mmaph, access, i.HighPart, i.LowPart, + (SIZE_T)size); + if (!*buffer) { + char buf[256]; + st = (ham_status_t)GetLastError(); + /* make sure to release the mapping */ + (void)CloseHandle(m_mmaph); + m_mmaph = HAM_INVALID_FD; + ham_log(("MapViewOfFile failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + if (st == ERROR_NOT_ENOUGH_QUOTA) // not enough resources - fallback to r/w + throw Exception(HAM_LIMITS_REACHED); + throw Exception(HAM_IO_ERROR); + } +} + +void +File::munmap(void *buffer, size_t size) +{ + ham_status_t st; + + if (!UnmapViewOfFile(buffer)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("UnMapViewOfFile failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + if (m_mmaph != HAM_INVALID_FD) { + if (!CloseHandle(m_mmaph)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("CloseHandle failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + } + + m_mmaph = HAM_INVALID_FD; +} + +void +File::pread(uint64_t addr, void *buffer, size_t len) +{ + ham_status_t st; + OVERLAPPED ov = { 0 }; + ov.Offset = (DWORD)addr; + ov.OffsetHigh = addr >> 32; + DWORD read; + if (!::ReadFile(m_fd, buffer, (DWORD)len, &read, &ov)) { + if (GetLastError() != ERROR_IO_PENDING) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("ReadFile failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + if (!::GetOverlappedResult(m_fd, &ov, &read, TRUE)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("GetOverlappedResult failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + } + + if (read != len) + throw Exception(HAM_IO_ERROR); +} + +void +File::pwrite(uint64_t addr, const void *buffer, size_t len) +{ + ham_status_t st; + OVERLAPPED ov = { 0 }; + ov.Offset = (DWORD)addr; + ov.OffsetHigh = addr >> 32; + DWORD written; + if (!::WriteFile(m_fd, buffer, (DWORD)len, &written, &ov)) { + if (GetLastError() != ERROR_IO_PENDING) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("WriteFile failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + if (!::GetOverlappedResult(m_fd, &ov, &written, TRUE)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("GetOverlappedResult failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + } + + if (written != len) + throw Exception(HAM_IO_ERROR); +} + +void +File::write(const void *buffer, size_t len) +{ + ham_status_t st; + DWORD written = 0; + + if (!WriteFile(m_fd, buffer, (DWORD)len, &written, 0)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("WriteFile failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + if (written != len) + throw Exception(HAM_IO_ERROR); +} + +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + +void +File::seek(uint64_t offset, int whence) +{ + DWORD st; + LARGE_INTEGER i; + i.QuadPart = offset; + + i.LowPart = ::SetFilePointer(m_fd, i.LowPart, &i.HighPart, whence); + if (i.LowPart == INVALID_SET_FILE_POINTER && + (st = GetLastError())!=NO_ERROR) { + char buf[256]; + ham_log(("SetFilePointer failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } +} + +uint64_t +File::tell() +{ + DWORD st; + LARGE_INTEGER i; + i.QuadPart = 0; + + i.LowPart = SetFilePointer(m_fd, i.LowPart, &i.HighPart, kSeekCur); + if (i.LowPart == INVALID_SET_FILE_POINTER && + (st = GetLastError()) != NO_ERROR) { + char buf[256]; + ham_log(("SetFilePointer failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + return ((size_t)i.QuadPart); +} + +#ifndef INVALID_FILE_SIZE +# define INVALID_FILE_SIZE ((DWORD)-1) +#endif + +uint64_t +File::get_file_size() +{ + ham_status_t st; + LARGE_INTEGER i; + i.QuadPart = 0; + i.LowPart = GetFileSize(m_fd, (LPDWORD)&i.HighPart); + + if (i.LowPart == INVALID_FILE_SIZE && (st = GetLastError()) != NO_ERROR) { + char buf[256]; + ham_log(("GetFileSize failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + return ((size_t)i.QuadPart); +} + +void +File::truncate(uint64_t newsize) +{ + File::seek(newsize, kSeekSet); + + if (!SetEndOfFile(m_fd)) { + char buf[256]; + ham_status_t st = (ham_status_t)GetLastError(); + ham_log(("SetEndOfFile failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } +} + +void +File::create(const char *filename, uint32_t mode) +{ + ham_status_t st; + DWORD share = 0; /* 1.1.0: default behaviour is exclusive locking */ + DWORD access = GENERIC_READ | GENERIC_WRITE; + ham_fd_t fd; + +#ifdef UNICODE + int fnameWlen = calc_wlen4str(filename); + WCHAR *wfilename = (WCHAR *)malloc(fnameWlen * sizeof(wfilename[0])); + if (!wfilename) + throw Exception(HAM_OUT_OF_MEMORY); + + /* translate ASCII filename to unicode */ + utf8_string(filename, wfilename, fnameWlen); + fd = (ham_fd_t)CreateFileW(wfilename, access, + share, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 0); + free(wfilename); +#else + fd = (ham_fd_t)CreateFileA(filename, access, + share, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 0); +#endif + + if (fd == INVALID_HANDLE_VALUE) { + char buf[256]; + st = (ham_status_t)GetLastError(); + if (st == ERROR_SHARING_VIOLATION) + throw Exception(HAM_WOULD_BLOCK); + ham_log(("CreateFile(%s, %x, %x, ...) (create) failed with OS status " + "%u (%s)", filename, access, share, st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + + m_fd = fd; +} + +void +File::flush() +{ + ham_status_t st; + + if (!FlushFileBuffers(m_fd)) { + char buf[256]; + st = (ham_status_t)GetLastError(); + ham_log(("FlushFileBuffers failed with OS status %u (%s)", + st, DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } +} + +void +File::open(const char *filename, bool read_only) +{ + ham_status_t st; + DWORD share = 0; /* 1.1.0: default behaviour is exclusive locking */ + DWORD access = read_only + ? GENERIC_READ + : (GENERIC_READ | GENERIC_WRITE); + DWORD dispo = OPEN_EXISTING; + DWORD osflags = 0; + ham_fd_t fd; + +#ifdef UNICODE + { + int fnameWlen = calc_wlen4str(filename); + WCHAR *wfilename = (WCHAR *)malloc(fnameWlen * sizeof(wfilename[0])); + if (!wfilename) + throw Exception(HAM_OUT_OF_MEMORY); + + /* translate ASCII filename to unicode */ + utf8_string(filename, wfilename, fnameWlen); + fd = (ham_fd_t)CreateFileW(wfilename, access, share, NULL, + dispo, osflags, 0); + free(wfilename); + } +#else + fd = (ham_fd_t)CreateFileA(filename, access, share, NULL, + dispo, osflags, 0); +#endif + + if (fd == INVALID_HANDLE_VALUE) { + char buf[256]; + fd = HAM_INVALID_FD; + st = (ham_status_t)GetLastError(); + ham_log(("CreateFile(%s, %x, %x, ...) (open) failed with OS status " + "%u (%s)", filename, access, share, + st, DisplayError(buf, sizeof(buf), st))); + if (st == ERROR_SHARING_VIOLATION) + throw Exception(HAM_WOULD_BLOCK); + throw Exception(st == ERROR_FILE_NOT_FOUND + ? HAM_FILE_NOT_FOUND + : HAM_IO_ERROR); + } + + m_fd = fd; +} + +void +File::close() +{ + if (m_fd != HAM_INVALID_FD) { + if (!CloseHandle((HANDLE)m_fd)) { + char buf[256]; + ham_status_t st = (ham_status_t)GetLastError(); + ham_log(("CloseHandle failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + m_fd = HAM_INVALID_FD; + } + + if (m_mmaph != HAM_INVALID_FD) { + if (!CloseHandle((HANDLE)m_mmaph)) { + char buf[256]; + ham_status_t st = (ham_status_t)GetLastError(); + ham_log(("CloseHandle failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + m_mmaph = HAM_INVALID_FD; + } +} + +void +Socket::connect(const char *hostname, uint16_t port, uint32_t timeout_sec) +{ + WORD sockVersion = MAKEWORD(1, 1); + WSADATA wsaData; + WSAStartup(sockVersion, &wsaData); + + ham_socket_t s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s < 0) { + ham_log(("failed creating socket: %s", strerror(errno))); + throw Exception(HAM_IO_ERROR); + } + + LPHOSTENT server = ::gethostbyname(hostname); + if (!server) { + ham_log(("unable to resolve hostname %s", hostname)); + ::closesocket(s); + throw Exception(HAM_NETWORK_ERROR); + } + + SOCKADDR_IN addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr = *((LPIN_ADDR)*server->h_addr_list); + addr.sin_port = htons(port); + if (::connect(s, (LPSOCKADDR)&addr, sizeof(addr)) < 0) { + ham_log(("unable to connect to %s:%d: %s", hostname, (int)port, + strerror(errno))); + ::closesocket(s); + throw Exception(HAM_NETWORK_ERROR); + } + + if (timeout_sec) { + struct timeval tv; + tv.tv_sec = timeout_sec; + tv.tv_usec = 0; + if (::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0) { + char buf[256]; + ham_log(("unable to set socket timeout to %u sec: %u/%s", timeout_sec, + WSAGetLastError(), DisplayError(buf, sizeof(buf), + WSAGetLastError()))); + // fall through, this is not critical + } + } + + m_socket = s; +} + +void +Socket::send(const uint8_t *data, size_t len) +{ + size_t sent = 0; + char buf[256]; + ham_status_t st; + + while (sent != len) { + int s = ::send(m_socket, (const char *)(data + sent), len - sent, 0); + if (s <= 0) { + st = (ham_status_t)GetLastError(); + ham_log(("send failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + sent += s; + } +} + +void +Socket::recv(uint8_t *data, size_t len) +{ + size_t read = 0; + char buf[256]; + ham_status_t st; + + while (read != len) { + int r = ::recv(m_socket, (char *)(data + read), len - read, 0); + if (r <= 0) { + st = (ham_status_t)GetLastError(); + ham_log(("recv failed with OS status %u (%s)", st, + DisplayError(buf, sizeof(buf), st))); + throw Exception(HAM_IO_ERROR); + } + read += r; + } +} + +void +Socket::close() +{ + if (m_socket != HAM_INVALID_FD) { + if (::closesocket(m_socket) == -1) + throw Exception(HAM_IO_ERROR); + m_socket = HAM_INVALID_FD; + } +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1os/socket.h b/plugins/Dbx_kv/src/hamsterdb/src/1os/socket.h new file mode 100644 index 0000000000..0acdfdd14e --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1os/socket.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A simple wrapper around a tcp socket handle. Throws exceptions in + * case of errors + * + * @exception_safe: basic + * @thread_safe: unknown + */ + +#ifndef HAM_SOCKET_H +#define HAM_SOCKET_H + +#include "0root/root.h" + +#include <stdio.h> +#include <limits.h> + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/os.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Socket +{ + public: + // Constructor creates an empty socket + Socket() + : m_socket(HAM_INVALID_FD) { + } + + // Destructor closes the socket + ~Socket() { + close(); + } + + // Connects to a remote host + void connect(const char *hostname, uint16_t port, uint32_t timeout_sec); + + // Sends data to the connected server + void send(const uint8_t *data, size_t len); + + // Receives data from the connected server; blocking! + void recv(uint8_t *data, size_t len); + + // Closes the connection; no problem if socket was already closed + void close(); + + private: + ham_socket_t m_socket; +}; + +} // namespace hamsterdb + +#endif /* HAM_SOCKET_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/1rb/rb.h b/plugins/Dbx_kv/src/hamsterdb/src/1rb/rb.h new file mode 100644 index 0000000000..fcf0c135d5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/1rb/rb.h @@ -0,0 +1,977 @@ +/*- + ******************************************************************************* + * + * cpp macro implementation of left-leaning 2-3 red-black trees. Parent + * pointers are not used, and color bits are stored in the least significant + * bit of right-child pointers (if RB_COMPACT is defined), thus making node + * linkage as compact as is possible for red-black trees. + * + * Usage: + * + * #include <stdint.h> + * #include <stdbool.h> + * #define NDEBUG // (Optional, see assert(3).) + * #include <assert.h> + * #define RB_COMPACT // (Optional, embed color bits in right-child pointers.) + * #include <rb.h> + * ... + * + ******************************************************************************* + */ + +#ifndef RB_H_ +#define RB_H_ + +#include "0root/root.h" + +#ifndef HAM_OS_WIN32 +# include <stdint.h> +# include <sys/cdefs.h> +#endif +#include <assert.h> + +#ifdef RB_COMPACT +/* Node structure. */ +#define rb_node(a_type) \ +struct { \ + a_type *rbn_left; \ + a_type *rbn_right_red; \ +} +#else +#define rb_node(a_type) \ +struct { \ + a_type *rbn_left; \ + a_type *rbn_right; \ + bool rbn_red; \ +} +#endif + +/* Root structure. */ +#define rbt(a_type) \ +struct { \ + a_type *rbt_root; \ + a_type rbt_nil; \ +} + +/* Left accessors. */ +#define rbtn_left_get(a_type, a_field, a_node) \ + ((a_node)->a_field.rbn_left) +#define rbtn_left_set(a_type, a_field, a_node, a_left) do { \ + (a_node)->a_field.rbn_left = a_left; \ +} while (0) + +#ifdef RB_COMPACT +/* Right accessors. */ +#define rbtn_right_get(a_type, a_field, a_node) \ + ((a_type *) (((intptr_t) (a_node)->a_field.rbn_right_red) \ + & ((ssize_t)-2))) +#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \ + (a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) a_right) \ + | (((uintptr_t) (a_node)->a_field.rbn_right_red) & ((size_t)1))); \ +} while (0) + +/* Color accessors. */ +#define rbtn_red_get(a_type, a_field, a_node) \ + ((bool) (((uintptr_t) (a_node)->a_field.rbn_right_red) \ + & ((size_t)1))) +#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \ + (a_node)->a_field.rbn_right_red = (a_type *) ((((intptr_t) \ + (a_node)->a_field.rbn_right_red) & ((ssize_t)-2)) \ + | ((ssize_t)a_red)); \ +} while (0) +#define rbtn_red_set(a_type, a_field, a_node) do { \ + (a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) \ + (a_node)->a_field.rbn_right_red) | ((size_t)1)); \ +} while (0) +#define rbtn_black_set(a_type, a_field, a_node) do { \ + (a_node)->a_field.rbn_right_red = (a_type *) (((intptr_t) \ + (a_node)->a_field.rbn_right_red) & ((ssize_t)-2)); \ +} while (0) +#else +/* Right accessors. */ +#define rbtn_right_get(a_type, a_field, a_node) \ + ((a_node)->a_field.rbn_right) +#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \ + (a_node)->a_field.rbn_right = a_right; \ +} while (0) + +/* Color accessors. */ +#define rbtn_red_get(a_type, a_field, a_node) \ + ((a_node)->a_field.rbn_red) +#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \ + (a_node)->a_field.rbn_red = (a_red); \ +} while (0) +#define rbtn_red_set(a_type, a_field, a_node) do { \ + (a_node)->a_field.rbn_red = true; \ +} while (0) +#define rbtn_black_set(a_type, a_field, a_node) do { \ + (a_node)->a_field.rbn_red = false; \ +} while (0) +#endif + +/* Node initializer. */ +#define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \ + rbtn_left_set(a_type, a_field, (a_node), &(a_rbt)->rbt_nil); \ + rbtn_right_set(a_type, a_field, (a_node), &(a_rbt)->rbt_nil); \ + rbtn_red_set(a_type, a_field, (a_node)); \ +} while (0) + +/* Tree initializer. */ +#define rb_new(a_type, a_field, a_rbt) do { \ + (a_rbt)->rbt_root = &(a_rbt)->rbt_nil; \ + rbt_node_new(a_type, a_field, a_rbt, &(a_rbt)->rbt_nil); \ + rbtn_black_set(a_type, a_field, &(a_rbt)->rbt_nil); \ +} while (0) + +/* Internal utility macros. */ +#define rbtn_first(a_type, a_field, a_rbt, a_root, r_node) do { \ + (r_node) = (a_root); \ + if ((r_node) != &(a_rbt)->rbt_nil) { \ + for (; \ + rbtn_left_get(a_type, a_field, (r_node)) != &(a_rbt)->rbt_nil;\ + (r_node) = rbtn_left_get(a_type, a_field, (r_node))) { \ + } \ + } \ +} while (0) + +#define rbtn_last(a_type, a_field, a_rbt, a_root, r_node) do { \ + (r_node) = (a_root); \ + if ((r_node) != &(a_rbt)->rbt_nil) { \ + for (; rbtn_right_get(a_type, a_field, (r_node)) != \ + &(a_rbt)->rbt_nil; (r_node) = rbtn_right_get(a_type, a_field, \ + (r_node))) { \ + } \ + } \ +} while (0) + +#define rbtn_rotate_left(a_type, a_field, a_node, r_node) do { \ + (r_node) = rbtn_right_get(a_type, a_field, (a_node)); \ + rbtn_right_set(a_type, a_field, (a_node), \ + rbtn_left_get(a_type, a_field, (r_node))); \ + rbtn_left_set(a_type, a_field, (r_node), (a_node)); \ +} while (0) + +#define rbtn_rotate_right(a_type, a_field, a_node, r_node) do { \ + (r_node) = rbtn_left_get(a_type, a_field, (a_node)); \ + rbtn_left_set(a_type, a_field, (a_node), \ + rbtn_right_get(a_type, a_field, (r_node))); \ + rbtn_right_set(a_type, a_field, (r_node), (a_node)); \ +} while (0) + +/* + * The rb_proto() macro generates function prototypes that correspond to the + * functions generated by an equivalently parameterized call to rb_gen(). + */ + +#define rb_proto(a_attr, a_prefix, a_rbt_type, a_type) \ +a_attr void \ +a_prefix##new(a_rbt_type *rbtree); \ +a_attr a_type * \ +a_prefix##first(a_rbt_type *rbtree); \ +a_attr a_type * \ +a_prefix##last(a_rbt_type *rbtree); \ +a_attr a_type * \ +a_prefix##next(a_rbt_type *rbtree, a_type *node); \ +a_attr a_type * \ +a_prefix##prev(a_rbt_type *rbtree, a_type *node); \ +a_attr a_type * \ +a_prefix##search(a_rbt_type *rbtree, a_type *key); \ +a_attr a_type * \ +a_prefix##nsearch(a_rbt_type *rbtree, a_type *key); \ +a_attr a_type * \ +a_prefix##psearch(a_rbt_type *rbtree, a_type *key); \ +a_attr void \ +a_prefix##insert(a_rbt_type *rbtree, a_type *node); \ +a_attr void \ +a_prefix##remove(a_rbt_type *rbtree, a_type *node);/* \ +a_attr a_type * \ +a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \ + a_rbt_type *, a_type *, void *), void *arg); \ +a_attr a_type * \ +a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \ + a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg);*/ + +/* + * The rb_gen() macro generates a type-specific red-black tree implementation, + * based on the above cpp macros. + * + * Arguments: + * + * a_attr : Function attribute for generated functions (ex: static). + * a_prefix : Prefix for generated functions (ex: ex_). + * a_rb_type : Type for red-black tree data structure (ex: ex_t). + * a_type : Type for red-black tree node data structure (ex: ex_node_t). + * a_field : Name of red-black tree node linkage (ex: ex_link). + * a_cmp : Node comparison function name, with the following prototype: + * int (a_cmp *)(a_type *a_node, a_type *a_other); + * ^^^^^^ + * or a_key + * Interpretation of comparision function return values: + * -1 : a_node < a_other + * 0 : a_node == a_other + * 1 : a_node > a_other + * In all cases, the a_node or a_key macro argument is the first + * argument to the comparison function, which makes it possible + * to write comparison functions that treat the first argument + * specially. + * + * Assuming the following setup: + * + * typedef struct ex_node_s ex_node_t; + * struct ex_node_s { + * rb_node(ex_node_t) ex_link; + * }; + * typedef rbt(ex_node_t) ex_t; + * rb_gen(static, ex_, ex_t, ex_node_t, ex_link, ex_cmp) + * + * The following API is generated: + * + * static void + * ex_new(ex_t *tree); + * Description: Initialize a red-black tree structure. + * Args: + * tree: Pointer to an uninitialized red-black tree object. + * + * static ex_node_t * + * ex_first(ex_t *tree); + * static ex_node_t * + * ex_last(ex_t *tree); + * Description: Get the first/last node in tree. + * Args: + * tree: Pointer to an initialized red-black tree object. + * Ret: First/last node in tree, or NULL if tree is empty. + * + * static ex_node_t * + * ex_next(ex_t *tree, ex_node_t *node); + * static ex_node_t * + * ex_prev(ex_t *tree, ex_node_t *node); + * Description: Get node's successor/predecessor. + * Args: + * tree: Pointer to an initialized red-black tree object. + * node: A node in tree. + * Ret: node's successor/predecessor in tree, or NULL if node is + * last/first. + * + * static ex_node_t * + * ex_search(ex_t *tree, ex_node_t *key); + * Description: Search for node that matches key. + * Args: + * tree: Pointer to an initialized red-black tree object. + * key : Search key. + * Ret: Node in tree that matches key, or NULL if no match. + * + * static ex_node_t * + * ex_nsearch(ex_t *tree, ex_node_t *key); + * static ex_node_t * + * ex_psearch(ex_t *tree, ex_node_t *key); + * Description: Search for node that matches key. If no match is found, + * return what would be key's successor/predecessor, were + * key in tree. + * Args: + * tree: Pointer to an initialized red-black tree object. + * key : Search key. + * Ret: Node in tree that matches key, or if no match, hypothetical node's + * successor/predecessor (NULL if no successor/predecessor). + * + * static void + * ex_insert(ex_t *tree, ex_node_t *node); + * Description: Insert node into tree. + * Args: + * tree: Pointer to an initialized red-black tree object. + * node: Node to be inserted into tree. + * + * static void + * ex_remove(ex_t *tree, ex_node_t *node); + * Description: Remove node from tree. + * Args: + * tree: Pointer to an initialized red-black tree object. + * node: Node in tree to be removed. + * + * static ex_node_t * + * ex_iter(ex_t *tree, ex_node_t *start, ex_node_t *(*cb)(ex_t *, + * ex_node_t *, void *), void *arg); + * static ex_node_t * + * ex_reverse_iter(ex_t *tree, ex_node_t *start, ex_node *(*cb)(ex_t *, + * ex_node_t *, void *), void *arg); + * Description: Iterate forward/backward over tree, starting at node. If + * tree is modified, iteration must be immediately + * terminated by the callback function that causes the + * modification. + * Args: + * tree : Pointer to an initialized red-black tree object. + * start: Node at which to start iteration, or NULL to start at + * first/last node. + * cb : Callback function, which is called for each node during + * iteration. Under normal circumstances the callback function + * should return NULL, which causes iteration to continue. If a + * callback function returns non-NULL, iteration is immediately + * terminated and the non-NULL return value is returned by the + * iterator. This is useful for re-starting iteration after + * modifying tree. + * arg : Opaque pointer passed to cb(). + * Ret: NULL if iteration completed, or the non-NULL callback return value + * that caused termination of the iteration. + */ +#define rb_gen(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp) \ +a_attr void \ +a_prefix##new(a_rbt_type *rbtree) { \ + rb_new(a_type, a_field, rbtree); \ +} \ +a_attr a_type * \ +a_prefix##first(a_rbt_type *rbtree) { \ + a_type *ret; \ + rbtn_first(a_type, a_field, rbtree, rbtree->rbt_root, ret); \ + if (ret == &rbtree->rbt_nil) { \ + ret = NULL; \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##last(a_rbt_type *rbtree) { \ + a_type *ret; \ + rbtn_last(a_type, a_field, rbtree, rbtree->rbt_root, ret); \ + if (ret == &rbtree->rbt_nil) { \ + ret = NULL; \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##next(a_rbt_type *rbtree, a_type *node) { \ + a_type *ret; \ + if (rbtn_right_get(a_type, a_field, node) != &rbtree->rbt_nil) { \ + rbtn_first(a_type, a_field, rbtree, rbtn_right_get(a_type, \ + a_field, node), ret); \ + } else { \ + a_type *tnode = rbtree->rbt_root; \ + assert(tnode != &rbtree->rbt_nil); \ + ret = &rbtree->rbt_nil; \ + while (true) { \ + int cmp = (a_cmp)(node, tnode); \ + if (cmp < 0) { \ + ret = tnode; \ + tnode = rbtn_left_get(a_type, a_field, tnode); \ + } else if (cmp > 0) { \ + tnode = rbtn_right_get(a_type, a_field, tnode); \ + } else { \ + break; \ + } \ + assert(tnode != &rbtree->rbt_nil); \ + } \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = (NULL); \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##prev(a_rbt_type *rbtree, a_type *node) { \ + a_type *ret; \ + if (rbtn_left_get(a_type, a_field, node) != &rbtree->rbt_nil) { \ + rbtn_last(a_type, a_field, rbtree, rbtn_left_get(a_type, \ + a_field, node), ret); \ + } else { \ + a_type *tnode = rbtree->rbt_root; \ + assert(tnode != &rbtree->rbt_nil); \ + ret = &rbtree->rbt_nil; \ + while (true) { \ + int cmp = (a_cmp)(node, tnode); \ + if (cmp < 0) { \ + tnode = rbtn_left_get(a_type, a_field, tnode); \ + } else if (cmp > 0) { \ + ret = tnode; \ + tnode = rbtn_right_get(a_type, a_field, tnode); \ + } else { \ + break; \ + } \ + assert(tnode != &rbtree->rbt_nil); \ + } \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = (NULL); \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##search(a_rbt_type *rbtree, a_type *key) { \ + a_type *ret; \ + int cmp; \ + ret = rbtree->rbt_root; \ + while (ret != &rbtree->rbt_nil \ + && (cmp = (a_cmp)(key, ret)) != 0) { \ + if (cmp < 0) { \ + ret = rbtn_left_get(a_type, a_field, ret); \ + } else { \ + ret = rbtn_right_get(a_type, a_field, ret); \ + } \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = (NULL); \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##nsearch(a_rbt_type *rbtree, a_type *key) { \ + a_type *ret; \ + a_type *tnode = rbtree->rbt_root; \ + ret = &rbtree->rbt_nil; \ + while (tnode != &rbtree->rbt_nil) { \ + int cmp = (a_cmp)(key, tnode); \ + if (cmp < 0) { \ + ret = tnode; \ + tnode = rbtn_left_get(a_type, a_field, tnode); \ + } else if (cmp > 0) { \ + tnode = rbtn_right_get(a_type, a_field, tnode); \ + } else { \ + ret = tnode; \ + break; \ + } \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = (NULL); \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##psearch(a_rbt_type *rbtree, a_type *key) { \ + a_type *ret; \ + a_type *tnode = rbtree->rbt_root; \ + ret = &rbtree->rbt_nil; \ + while (tnode != &rbtree->rbt_nil) { \ + int cmp = (a_cmp)(key, tnode); \ + if (cmp < 0) { \ + tnode = rbtn_left_get(a_type, a_field, tnode); \ + } else if (cmp > 0) { \ + ret = tnode; \ + tnode = rbtn_right_get(a_type, a_field, tnode); \ + } else { \ + ret = tnode; \ + break; \ + } \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = (NULL); \ + } \ + return (ret); \ +} \ +a_attr void \ +a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \ + struct { \ + a_type *node; \ + int cmp; \ + } path[sizeof(void *) << 4], *pathp; \ + rbt_node_new(a_type, a_field, rbtree, node); \ + /* Wind. */ \ + path->node = rbtree->rbt_root; \ + for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) { \ + int cmp = pathp->cmp = a_cmp(node, pathp->node); \ + assert(cmp != 0); \ + if (cmp < 0) { \ + pathp[1].node = rbtn_left_get(a_type, a_field, \ + pathp->node); \ + } else { \ + pathp[1].node = rbtn_right_get(a_type, a_field, \ + pathp->node); \ + } \ + } \ + pathp->node = node; \ + /* Unwind. */ \ + for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--) { \ + a_type *cnode = pathp->node; \ + if (pathp->cmp < 0) { \ + a_type *left = pathp[1].node; \ + rbtn_left_set(a_type, a_field, cnode, left); \ + if (rbtn_red_get(a_type, a_field, left)) { \ + a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ + if (rbtn_red_get(a_type, a_field, leftleft)) { \ + /* Fix up 4-node. */ \ + a_type *tnode; \ + rbtn_black_set(a_type, a_field, leftleft); \ + rbtn_rotate_right(a_type, a_field, cnode, tnode); \ + cnode = tnode; \ + } \ + } else { \ + return; \ + } \ + } else { \ + a_type *right = pathp[1].node; \ + rbtn_right_set(a_type, a_field, cnode, right); \ + if (rbtn_red_get(a_type, a_field, right)) { \ + a_type *left = rbtn_left_get(a_type, a_field, cnode); \ + if (rbtn_red_get(a_type, a_field, left)) { \ + /* Split 4-node. */ \ + rbtn_black_set(a_type, a_field, left); \ + rbtn_black_set(a_type, a_field, right); \ + rbtn_red_set(a_type, a_field, cnode); \ + } else { \ + /* Lean left. */ \ + a_type *tnode; \ + bool tred = rbtn_red_get(a_type, a_field, cnode); \ + rbtn_rotate_left(a_type, a_field, cnode, tnode); \ + rbtn_color_set(a_type, a_field, tnode, tred); \ + rbtn_red_set(a_type, a_field, cnode); \ + cnode = tnode; \ + } \ + } else { \ + return; \ + } \ + } \ + pathp->node = cnode; \ + } \ + /* Set root, and make it black. */ \ + rbtree->rbt_root = path->node; \ + rbtn_black_set(a_type, a_field, rbtree->rbt_root); \ +} \ +a_attr void \ +a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \ + struct { \ + a_type *node; \ + int cmp; \ + } *pathp, *nodep, path[sizeof(void *) << 4]; \ + /* Wind. */ \ + nodep = NULL; /* Silence compiler warning. */ \ + path->node = rbtree->rbt_root; \ + for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) { \ + int cmp = pathp->cmp = a_cmp(node, pathp->node); \ + if (cmp < 0) { \ + pathp[1].node = rbtn_left_get(a_type, a_field, \ + pathp->node); \ + } else { \ + pathp[1].node = rbtn_right_get(a_type, a_field, \ + pathp->node); \ + if (cmp == 0) { \ + /* Find node's successor, in preparation for swap. */ \ + pathp->cmp = 1; \ + nodep = pathp; \ + for (pathp++; pathp->node != &rbtree->rbt_nil; \ + pathp++) { \ + pathp->cmp = -1; \ + pathp[1].node = rbtn_left_get(a_type, a_field, \ + pathp->node); \ + } \ + break; \ + } \ + } \ + } \ + assert(nodep->node == node); \ + pathp--; \ + if (pathp->node != node) { \ + /* Swap node with its successor. */ \ + bool tred = rbtn_red_get(a_type, a_field, pathp->node); \ + rbtn_color_set(a_type, a_field, pathp->node, \ + rbtn_red_get(a_type, a_field, node)); \ + rbtn_left_set(a_type, a_field, pathp->node, \ + rbtn_left_get(a_type, a_field, node)); \ + /* If node's successor is its right child, the following code */\ + /* will do the wrong thing for the right child pointer. */\ + /* However, it doesn't matter, because the pointer will be */\ + /* properly set when the successor is pruned. */\ + rbtn_right_set(a_type, a_field, pathp->node, \ + rbtn_right_get(a_type, a_field, node)); \ + rbtn_color_set(a_type, a_field, node, tred); \ + /* The pruned leaf node's child pointers are never accessed */\ + /* again, so don't bother setting them to nil. */\ + nodep->node = pathp->node; \ + pathp->node = node; \ + if (nodep == path) { \ + rbtree->rbt_root = nodep->node; \ + } else { \ + if (nodep[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, nodep[-1].node, \ + nodep->node); \ + } else { \ + rbtn_right_set(a_type, a_field, nodep[-1].node, \ + nodep->node); \ + } \ + } \ + } else { \ + a_type *left = rbtn_left_get(a_type, a_field, node); \ + if (left != &rbtree->rbt_nil) { \ + /* node has no successor, but it has a left child. */\ + /* Splice node out, without losing the left child. */\ + assert(rbtn_red_get(a_type, a_field, node) == false); \ + assert(rbtn_red_get(a_type, a_field, left)); \ + rbtn_black_set(a_type, a_field, left); \ + if (pathp == path) { \ + rbtree->rbt_root = left; \ + } else { \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, pathp[-1].node, \ + left); \ + } else { \ + rbtn_right_set(a_type, a_field, pathp[-1].node, \ + left); \ + } \ + } \ + return; \ + } else if (pathp == path) { \ + /* The tree only contained one node. */ \ + rbtree->rbt_root = &rbtree->rbt_nil; \ + return; \ + } \ + } \ + if (rbtn_red_get(a_type, a_field, pathp->node)) { \ + /* Prune red node, which requires no fixup. */ \ + assert(pathp[-1].cmp < 0); \ + rbtn_left_set(a_type, a_field, pathp[-1].node, \ + &rbtree->rbt_nil); \ + return; \ + } \ + /* The node to be pruned is black, so unwind until balance is */\ + /* restored. */\ + pathp->node = &rbtree->rbt_nil; \ + for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--) { \ + assert(pathp->cmp != 0); \ + if (pathp->cmp < 0) { \ + rbtn_left_set(a_type, a_field, pathp->node, \ + pathp[1].node); \ + assert(rbtn_red_get(a_type, a_field, pathp[1].node) \ + == false); \ + if (rbtn_red_get(a_type, a_field, pathp->node)) { \ + a_type *right = rbtn_right_get(a_type, a_field, \ + pathp->node); \ + a_type *rightleft = rbtn_left_get(a_type, a_field, \ + right); \ + a_type *tnode; \ + if (rbtn_red_get(a_type, a_field, rightleft)) { \ + /* In the following diagrams, ||, //, and \\ */\ + /* indicate the path to the removed node. */\ + /* */\ + /* || */\ + /* pathp(r) */\ + /* // \ */\ + /* (b) (b) */\ + /* / */\ + /* (r) */\ + /* */\ + rbtn_black_set(a_type, a_field, pathp->node); \ + rbtn_rotate_right(a_type, a_field, right, tnode); \ + rbtn_right_set(a_type, a_field, pathp->node, tnode);\ + rbtn_rotate_left(a_type, a_field, pathp->node, \ + tnode); \ + } else { \ + /* || */\ + /* pathp(r) */\ + /* // \ */\ + /* (b) (b) */\ + /* / */\ + /* (b) */\ + /* */\ + rbtn_rotate_left(a_type, a_field, pathp->node, \ + tnode); \ + } \ + /* Balance restored, but rotation modified subtree */\ + /* root. */\ + assert((uintptr_t)pathp > (uintptr_t)path); \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } else { \ + rbtn_right_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } \ + return; \ + } else { \ + a_type *right = rbtn_right_get(a_type, a_field, \ + pathp->node); \ + a_type *rightleft = rbtn_left_get(a_type, a_field, \ + right); \ + if (rbtn_red_get(a_type, a_field, rightleft)) { \ + /* || */\ + /* pathp(b) */\ + /* // \ */\ + /* (b) (b) */\ + /* / */\ + /* (r) */\ + a_type *tnode; \ + rbtn_black_set(a_type, a_field, rightleft); \ + rbtn_rotate_right(a_type, a_field, right, tnode); \ + rbtn_right_set(a_type, a_field, pathp->node, tnode);\ + rbtn_rotate_left(a_type, a_field, pathp->node, \ + tnode); \ + /* Balance restored, but rotation modified */\ + /* subree root, which may actually be the tree */\ + /* root. */\ + if (pathp == path) { \ + /* Set root. */ \ + rbtree->rbt_root = tnode; \ + } else { \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, \ + pathp[-1].node, tnode); \ + } else { \ + rbtn_right_set(a_type, a_field, \ + pathp[-1].node, tnode); \ + } \ + } \ + return; \ + } else { \ + /* || */\ + /* pathp(b) */\ + /* // \ */\ + /* (b) (b) */\ + /* / */\ + /* (b) */\ + a_type *tnode; \ + rbtn_red_set(a_type, a_field, pathp->node); \ + rbtn_rotate_left(a_type, a_field, pathp->node, \ + tnode); \ + pathp->node = tnode; \ + } \ + } \ + } else { \ + a_type *left; \ + rbtn_right_set(a_type, a_field, pathp->node, \ + pathp[1].node); \ + left = rbtn_left_get(a_type, a_field, pathp->node); \ + if (rbtn_red_get(a_type, a_field, left)) { \ + a_type *tnode; \ + a_type *leftright = rbtn_right_get(a_type, a_field, \ + left); \ + a_type *leftrightleft = rbtn_left_get(a_type, a_field, \ + leftright); \ + if (rbtn_red_get(a_type, a_field, leftrightleft)) { \ + /* || */\ + /* pathp(b) */\ + /* / \\ */\ + /* (r) (b) */\ + /* \ */\ + /* (b) */\ + /* / */\ + /* (r) */\ + a_type *unode; \ + rbtn_black_set(a_type, a_field, leftrightleft); \ + rbtn_rotate_right(a_type, a_field, pathp->node, \ + unode); \ + rbtn_rotate_right(a_type, a_field, pathp->node, \ + tnode); \ + rbtn_right_set(a_type, a_field, unode, tnode); \ + rbtn_rotate_left(a_type, a_field, unode, tnode); \ + } else { \ + /* || */\ + /* pathp(b) */\ + /* / \\ */\ + /* (r) (b) */\ + /* \ */\ + /* (b) */\ + /* / */\ + /* (b) */\ + assert(leftright != &rbtree->rbt_nil); \ + rbtn_red_set(a_type, a_field, leftright); \ + rbtn_rotate_right(a_type, a_field, pathp->node, \ + tnode); \ + rbtn_black_set(a_type, a_field, tnode); \ + } \ + /* Balance restored, but rotation modified subtree */\ + /* root, which may actually be the tree root. */\ + if (pathp == path) { \ + /* Set root. */ \ + rbtree->rbt_root = tnode; \ + } else { \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } else { \ + rbtn_right_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } \ + } \ + return; \ + } else if (rbtn_red_get(a_type, a_field, pathp->node)) { \ + a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ + if (rbtn_red_get(a_type, a_field, leftleft)) { \ + /* || */\ + /* pathp(r) */\ + /* / \\ */\ + /* (b) (b) */\ + /* / */\ + /* (r) */\ + a_type *tnode; \ + rbtn_black_set(a_type, a_field, pathp->node); \ + rbtn_red_set(a_type, a_field, left); \ + rbtn_black_set(a_type, a_field, leftleft); \ + rbtn_rotate_right(a_type, a_field, pathp->node, \ + tnode); \ + /* Balance restored, but rotation modified */\ + /* subtree root. */\ + assert((uintptr_t)pathp > (uintptr_t)path); \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } else { \ + rbtn_right_set(a_type, a_field, pathp[-1].node, \ + tnode); \ + } \ + return; \ + } else { \ + /* || */\ + /* pathp(r) */\ + /* / \\ */\ + /* (b) (b) */\ + /* / */\ + /* (b) */\ + rbtn_red_set(a_type, a_field, left); \ + rbtn_black_set(a_type, a_field, pathp->node); \ + /* Balance restored. */ \ + return; \ + } \ + } else { \ + a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ + if (rbtn_red_get(a_type, a_field, leftleft)) { \ + /* || */\ + /* pathp(b) */\ + /* / \\ */\ + /* (b) (b) */\ + /* / */\ + /* (r) */\ + a_type *tnode; \ + rbtn_black_set(a_type, a_field, leftleft); \ + rbtn_rotate_right(a_type, a_field, pathp->node, \ + tnode); \ + /* Balance restored, but rotation modified */\ + /* subtree root, which may actually be the tree */\ + /* root. */\ + if (pathp == path) { \ + /* Set root. */ \ + rbtree->rbt_root = tnode; \ + } else { \ + if (pathp[-1].cmp < 0) { \ + rbtn_left_set(a_type, a_field, \ + pathp[-1].node, tnode); \ + } else { \ + rbtn_right_set(a_type, a_field, \ + pathp[-1].node, tnode); \ + } \ + } \ + return; \ + } else { \ + /* || */\ + /* pathp(b) */\ + /* / \\ */\ + /* (b) (b) */\ + /* / */\ + /* (b) */\ + rbtn_red_set(a_type, a_field, left); \ + } \ + } \ + } \ + } \ + /* Set root. */ \ + rbtree->rbt_root = path->node; \ + assert(rbtn_red_get(a_type, a_field, rbtree->rbt_root) == false); \ +}/* \ +a_attr a_type * \ +a_prefix##iter_recurse(a_rbt_type *rbtree, a_type *node, \ + a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ + if (node == &rbtree->rbt_nil) { \ + return (&rbtree->rbt_nil); \ + } else { \ + a_type *ret; \ + if ((ret = a_prefix##iter_recurse(rbtree, rbtn_left_get(a_type, \ + a_field, node), cb, arg)) != &rbtree->rbt_nil \ + || (ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ + a_field, node), cb, arg)); \ + } \ +} \ +a_attr a_type * \ +a_prefix##iter_start(a_rbt_type *rbtree, a_type *start, a_type *node, \ + a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ + int cmp = a_cmp(start, node); \ + if (cmp < 0) { \ + a_type *ret; \ + if ((ret = a_prefix##iter_start(rbtree, start, \ + rbtn_left_get(a_type, a_field, node), cb, arg)) != \ + &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ + a_field, node), cb, arg)); \ + } else if (cmp > 0) { \ + return (a_prefix##iter_start(rbtree, start, \ + rbtn_right_get(a_type, a_field, node), cb, arg)); \ + } else { \ + a_type *ret; \ + if ((ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ + a_field, node), cb, arg)); \ + } \ +} \ +a_attr a_type * \ +a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \ + a_rbt_type *, a_type *, void *), void *arg) { \ + a_type *ret; \ + if (start != NULL) { \ + ret = a_prefix##iter_start(rbtree, start, rbtree->rbt_root, \ + cb, arg); \ + } else { \ + ret = a_prefix##iter_recurse(rbtree, rbtree->rbt_root, cb, arg);\ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = NULL; \ + } \ + return (ret); \ +} \ +a_attr a_type * \ +a_prefix##reverse_iter_recurse(a_rbt_type *rbtree, a_type *node, \ + a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ + if (node == &rbtree->rbt_nil) { \ + return (&rbtree->rbt_nil); \ + } else { \ + a_type *ret; \ + if ((ret = a_prefix##reverse_iter_recurse(rbtree, \ + rbtn_right_get(a_type, a_field, node), cb, arg)) != \ + &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##reverse_iter_recurse(rbtree, \ + rbtn_left_get(a_type, a_field, node), cb, arg)); \ + } \ +} \ +a_attr a_type * \ +a_prefix##reverse_iter_start(a_rbt_type *rbtree, a_type *start, \ + a_type *node, a_type *(*cb)(a_rbt_type *, a_type *, void *), \ + void *arg) { \ + int cmp = a_cmp(start, node); \ + if (cmp > 0) { \ + a_type *ret; \ + if ((ret = a_prefix##reverse_iter_start(rbtree, start, \ + rbtn_right_get(a_type, a_field, node), cb, arg)) != \ + &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##reverse_iter_recurse(rbtree, \ + rbtn_left_get(a_type, a_field, node), cb, arg)); \ + } else if (cmp < 0) { \ + return (a_prefix##reverse_iter_start(rbtree, start, \ + rbtn_left_get(a_type, a_field, node), cb, arg)); \ + } else { \ + a_type *ret; \ + if ((ret = cb(rbtree, node, arg)) != NULL) { \ + return (ret); \ + } \ + return (a_prefix##reverse_iter_recurse(rbtree, \ + rbtn_left_get(a_type, a_field, node), cb, arg)); \ + } \ +} \ +a_attr a_type * \ +a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \ + a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ + a_type *ret; \ + if (start != NULL) { \ + ret = a_prefix##reverse_iter_start(rbtree, start, \ + rbtree->rbt_root, cb, arg); \ + } else { \ + ret = a_prefix##reverse_iter_recurse(rbtree, rbtree->rbt_root, \ + cb, arg); \ + } \ + if (ret == &rbtree->rbt_nil) { \ + ret = NULL; \ + } \ + return (ret); \ +}*/ + +#endif /* RB_H_ */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2config/db_config.h b/plugins/Dbx_kv/src/hamsterdb/src/2config/db_config.h new file mode 100644 index 0000000000..77f63944ef --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2config/db_config.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The configuration settings of a Database. + * + * @exception_safe nothrow + * @thread_safe no + */ + +#ifndef HAM_DB_CONFIG_H +#define HAM_DB_CONFIG_H + +#include "0root/root.h" + +#include <ham/types.h> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct DatabaseConfiguration +{ + // Constructor initializes with default values + DatabaseConfiguration() + : db_name(0), flags(0), key_type(HAM_TYPE_BINARY), + key_size(HAM_KEY_SIZE_UNLIMITED), record_size(HAM_RECORD_SIZE_UNLIMITED), + key_compressor(0), record_compressor(0) { + } + + // the database name + uint16_t db_name; + + // the database flags + uint32_t flags; + + // the key type + int key_type; + + // the key size (if specified) + size_t key_size; + + // the record size (if specified) + size_t record_size; + + // the algorithm for key compression + int key_compressor; + + // the algorithm for record compression + int record_compressor; + +}; + +} // namespace hamsterdb + +#endif // HAM_DB_CONFIG_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2config/env_config.h b/plugins/Dbx_kv/src/hamsterdb/src/2config/env_config.h new file mode 100644 index 0000000000..9db5de4771 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2config/env_config.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The configuration settings of an Environment. + * + * @exception_safe nothrow + * @thread_safe no + */ + +#ifndef HAM_ENV_CONFIG_H +#define HAM_ENV_CONFIG_H + +#include "0root/root.h" + +#include <string> +#include <limits> + +#include <ham/hamsterdb.h> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +#undef max + +namespace hamsterdb { + +struct EnvironmentConfiguration +{ + // Constructor initializes with default values + EnvironmentConfiguration() + : flags(0), file_mode(0644), max_databases(0), + page_size_bytes(HAM_DEFAULT_PAGE_SIZE), + cache_size_bytes(HAM_DEFAULT_CACHE_SIZE), + file_size_limit_bytes(std::numeric_limits<size_t>::max()), + remote_timeout_sec(0), journal_compressor(0), + is_encryption_enabled(false), journal_switch_threshold(0), + posix_advice(HAM_POSIX_FADVICE_NORMAL) { + } + + // the environment's flags + uint32_t flags; + + // the file mode + int file_mode; + + // the number of databases + int max_databases; + + // the page size (in bytes) + size_t page_size_bytes; + + // the cache size (in bytes) + uint64_t cache_size_bytes; + + // the file size limit (in bytes) + size_t file_size_limit_bytes; + + // the remote timeout (in seconds) + size_t remote_timeout_sec; + + // the path (or remote location) + std::string filename; + + // the path of the logfile + std::string log_filename; + + // the algorithm for journal compression + int journal_compressor; + + // true if AES encryption is enabled + bool is_encryption_enabled; + + // the AES encryption key + uint8_t encryption_key[16]; + + // threshold for switching journal files + size_t journal_switch_threshold; + + // parameter for posix_fadvise() + int posix_advice; +}; + +} // namespace hamsterdb + +#endif // HAM_ENV_CONFIG_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2device/device.h b/plugins/Dbx_kv/src/hamsterdb/src/2device/device.h new file mode 100644 index 0000000000..7550fad06a --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2device/device.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Device management; a device encapsulates the physical device, either a + * file or memory chunks (for in-memory-databases) + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_DEVICE_H +#define HAM_DEVICE_H + +#include "0root/root.h" + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "2config/env_config.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Page; + +class Device { + public: + // Constructor + Device(const EnvironmentConfiguration &config) + : m_config(config) { + } + + // virtual destructor + virtual ~Device() { + } + + // Returns the current page size + size_t page_size() const { + return (m_config.page_size_bytes); + } + + // Create a new device - called in ham_env_create + virtual void create() = 0; + + // Opens an existing device - called in ham_env_open + virtual void open() = 0; + + // Returns true if the device is open + virtual bool is_open() = 0; + + // Closes the device - called in ham_env_close + virtual void close() = 0; + + // Flushes the device - called in ham_env_flush + virtual void flush() = 0; + + // Truncate/resize the device + virtual void truncate(uint64_t new_size) = 0; + + // Returns the current file/storage size + virtual uint64_t file_size() = 0; + + // Seek position in a file + virtual void seek(uint64_t offset, int whence) = 0; + + // Tell the position in a file + virtual uint64_t tell() = 0; + + // Reads from the device; this function does not use mmap + virtual void read(uint64_t offset, void *buffer, size_t len) = 0; + + // Writes to the device; this function does not use mmap + virtual void write(uint64_t offset, void *buffer, size_t len) = 0; + + // Allocate storage from this device; this function + // will *NOT* use mmap. returns the offset of the allocated storage. + virtual uint64_t alloc(size_t len) = 0; + + // Reads a page from the device; this function CAN use mmap + virtual void read_page(Page *page, uint64_t address) = 0; + + // Writes a page to the device + virtual void write_page(Page *page) = 0; + + // Allocate storage for a page from this device; this function + // can use mmap if available + virtual void alloc_page(Page *page) = 0; + + // Frees a page on the device. + // The caller is responsible for flushing the page; the @ref free_page + // function will assert that the page is not dirty. + virtual void free_page(Page *page) = 0; + + // Returns true if the specified range is in mapped memory + virtual bool is_mapped(uint64_t file_offset, size_t size) const = 0; + + protected: + // the Environment configuration settings + const EnvironmentConfiguration &m_config; + + friend class DeviceTest; + friend class InMemoryDeviceTest; +}; + +} // namespace hamsterdb + +#endif /* HAM_DEVICE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2device/device_disk.h b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_disk.h new file mode 100644 index 0000000000..1bd62a904e --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_disk.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Device-implementation for disk-based files. Exception safety is "strong" + * for most operations, but currently it's possible that the Page is modified + * if DiskDevice::read_page fails in the middle. + * + * @exception_safe: basic/strong + * @thread_safe: no + */ + +#ifndef HAM_DEVICE_DISK_H +#define HAM_DEVICE_DISK_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/file.h" +#include "1mem/mem.h" +#include "2device/device.h" +#include "2page/page.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* + * a File-based device + */ +class DiskDevice : public Device { + struct State { + // the database file + File file; + + // pointer to the the mmapped data + uint8_t *mmapptr; + + // the size of mmapptr as used in mmap + uint64_t mapped_size; + + // the (cached) size of the file + uint64_t file_size; + }; + + public: + DiskDevice(const EnvironmentConfiguration &config) + : Device(config) { + State state; + state.mmapptr = 0; + state.mapped_size = 0; + state.file_size = 0; + std::swap(m_state, state); + } + + // Create a new device + virtual void create() { + File file; + file.create(m_config.filename.c_str(), m_config.file_mode); + file.set_posix_advice(m_config.posix_advice); + m_state.file = file; + } + + // opens an existing device + // + // tries to map the file; if it fails then continue with read/write + virtual void open() { + bool read_only = (m_config.flags & HAM_READ_ONLY) != 0; + + State state = m_state; + state.file.open(m_config.filename.c_str(), read_only); + state.file.set_posix_advice(m_config.posix_advice); + + // the file size which backs the mapped ptr + state.file_size = state.file.get_file_size(); + + if (m_config.flags & HAM_DISABLE_MMAP) { + std::swap(m_state, state); + return; + } + + // make sure we do not exceed the "real" size of the file, otherwise + // we crash when accessing memory which exceeds the mapping (at least + // on Win32) + size_t granularity = File::get_granularity(); + if (state.file_size == 0 || state.file_size % granularity) { + std::swap(m_state, state); + return; + } + + state.mapped_size = state.file_size; + state.file.mmap(0, state.mapped_size, read_only, &state.mmapptr); + std::swap(m_state, state); + } + + // returns true if the device is open + virtual bool is_open() { + return (m_state.file.is_open()); + } + + // closes the device + virtual void close() { + State state = m_state; + if (state.mmapptr) + state.file.munmap(state.mmapptr, state.mapped_size); + state.file.close(); + + std::swap(m_state, state); + } + + // flushes the device + virtual void flush() { + m_state.file.flush(); + } + + // truncate/resize the device + virtual void truncate(uint64_t new_file_size) { + if (new_file_size > m_config.file_size_limit_bytes) + throw Exception(HAM_LIMITS_REACHED); + m_state.file.truncate(new_file_size); + m_state.file_size = new_file_size; + } + + // get the current file/storage size + virtual uint64_t file_size() { + ham_assert(m_state.file_size == m_state.file.get_file_size()); + return (m_state.file_size); + } + + // seek to a position in a file + virtual void seek(uint64_t offset, int whence) { + m_state.file.seek(offset, whence); + } + + // tell the position in a file + virtual uint64_t tell() { + return (m_state.file.tell()); + } + + // reads from the device; this function does NOT use mmap + virtual void read(uint64_t offset, void *buffer, size_t len) { + m_state.file.pread(offset, buffer, len); + } + + // writes to the device; this function does not use mmap, + // and is responsible for writing the data is run through the file + // filters + virtual void write(uint64_t offset, void *buffer, size_t len) { + m_state.file.pwrite(offset, buffer, len); + } + + // allocate storage from this device; this function + // will *NOT* return mmapped memory + virtual uint64_t alloc(size_t len) { + uint64_t address = m_state.file_size; + truncate(address + len); + return ((uint64_t)address); + } + + // reads a page from the device; this function CAN return a + // pointer to mmapped memory + virtual void read_page(Page *page, uint64_t address) { + // if this page is in the mapped area: return a pointer into that area. + // otherwise fall back to read/write. + if (address < m_state.mapped_size && m_state.mmapptr != 0) { + // ok, this page is mapped. If the Page object has a memory buffer + // then free it; afterwards return a pointer into the mapped memory + page->free_buffer(); + // the following line will not throw a C++ exception, but can + // raise a signal. If that's the case then we don't catch it because + // something is seriously wrong and proper recovery is not possible. + page->assign_mapped_buffer(&m_state.mmapptr[address], address); + return; + } + + // this page is not in the mapped area; allocate a buffer + if (page->get_data() == 0) { + // note that |p| will not leak if file.pread() throws; |p| is stored + // in the |page| object and will be cleaned up by the caller in + // case of an exception. + uint8_t *p = Memory::allocate<uint8_t>(m_config.page_size_bytes); + page->assign_allocated_buffer(p, address); + } + + m_state.file.pread(address, page->get_data(), m_config.page_size_bytes); + } + + // writes a page to the device + virtual void write_page(Page *page) { + write(page->get_address(), page->get_data(), m_config.page_size_bytes); + } + + // Allocates storage for a page from this device; this function + // will *NOT* return mmapped memory + virtual void alloc_page(Page *page) { + uint64_t address = m_state.file_size; + + truncate(address + m_config.page_size_bytes); + page->set_address(address); + + // allocate a memory buffer + uint8_t *p = Memory::allocate<uint8_t>(m_config.page_size_bytes); + page->assign_allocated_buffer(p, address); + } + + // Frees a page on the device; plays counterpoint to |alloc_page| + virtual void free_page(Page *page) { + ham_assert(page->get_data() != 0); + page->free_buffer(); + } + + // Returns true if the specified range is in mapped memory + virtual bool is_mapped(uint64_t file_offset, size_t size) const { + return (file_offset + size <= m_state.mapped_size); + } + + private: + State m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_DEVICE_DISK_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2device/device_factory.h b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_factory.h new file mode 100644 index 0000000000..7cde29d5af --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_factory.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A factory for Device objects + * + * @exception_safe: strong + * @thread_safe: yes + */ + +#ifndef HAM_DEVICE_FACTORY_H +#define HAM_DEVICE_FACTORY_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "2config/env_config.h" +#include "2device/device_disk.h" +#include "2device/device_inmem.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct DeviceFactory { + // creates a new Device instance depending on the flags + static Device *create(const EnvironmentConfiguration &config) { + if (config.flags & HAM_IN_MEMORY) + return (new InMemoryDevice(config)); + else + return (new DiskDevice(config)); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_DEVICE_FACTORY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2device/device_inmem.h b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_inmem.h new file mode 100644 index 0000000000..3e2055148b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2device/device_inmem.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: strong + * @thread_safe: no + */ + +#ifndef HAM_DEVICE_INMEM_H +#define HAM_DEVICE_INMEM_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1mem/mem.h" +#include "2device/device.h" +#include "2page/page.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* + * an In-Memory device + */ +class InMemoryDevice : public Device { + struct State { + // flag whether this device was "opened" or is uninitialized + bool is_open; + + // the allocated bytes + uint64_t allocated_size; + }; + + public: + // constructor + InMemoryDevice(const EnvironmentConfiguration &config) + : Device(config) { + State state; + state.is_open = false; + state.allocated_size = 0; + std::swap(m_state, state); + } + + // Create a new device + virtual void create() { + m_state.is_open = true; + } + + // opens an existing device + virtual void open() { + ham_assert(!"can't open an in-memory-device"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // returns true if the device is open + virtual bool is_open() { + return (m_state.is_open); + } + + // closes the device + virtual void close() { + ham_assert(m_state.is_open); + m_state.is_open = false; + } + + // flushes the device + virtual void flush() { + } + + // truncate/resize the device + virtual void truncate(uint64_t newsize) { + } + + // get the current file/storage size + virtual uint64_t file_size() { + ham_assert(!"this operation is not possible for in-memory-databases"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // seek position in a file + virtual void seek(uint64_t offset, int whence) { + ham_assert(!"can't seek in an in-memory-device"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // tell the position in a file + virtual uint64_t tell() { + ham_assert(!"can't tell in an in-memory-device"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // reads from the device; this function does not use mmap + virtual void read(uint64_t offset, void *buffer, size_t len) { + ham_assert(!"operation is not possible for in-memory-databases"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // writes to the device + virtual void write(uint64_t offset, void *buffer, size_t len) { + ham_assert(!"operation is not possible for in-memory-databases"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // reads a page from the device + virtual void read_page(Page *page, uint64_t address) { + ham_assert(!"operation is not possible for in-memory-databases"); + throw Exception(HAM_NOT_IMPLEMENTED); + } + + // writes a page to the device + virtual void write_page(Page *page) { + } + + // allocate storage from this device; this function + // will *NOT* use mmap. + virtual uint64_t alloc(size_t size) { + if (m_state.allocated_size + size > m_config.file_size_limit_bytes) + throw Exception(HAM_LIMITS_REACHED); + + uint64_t retval = (uint64_t)Memory::allocate<uint8_t>(size); + m_state.allocated_size += size; + return (retval); + } + + // allocate storage for a page from this device + virtual void alloc_page(Page *page) { + ham_assert(page->get_data() == 0); + + size_t page_size = m_config.page_size_bytes; + if (m_state.allocated_size + page_size > m_config.file_size_limit_bytes) + throw Exception(HAM_LIMITS_REACHED); + + uint8_t *p = Memory::allocate<uint8_t>(page_size); + page->assign_allocated_buffer(p, (uint64_t)PTR_TO_U64(p)); + + m_state.allocated_size += page_size; + } + + // frees a page on the device; plays counterpoint to @ref alloc_page + virtual void free_page(Page *page) { + page->free_buffer(); + + ham_assert(m_state.allocated_size >= m_config.page_size_bytes); + m_state.allocated_size -= m_config.page_size_bytes; + } + + // Returns true if the specified range is in mapped memory + virtual bool is_mapped(uint64_t file_offset, size_t size) const { + return (false); + } + + // releases a chunk of memory previously allocated with alloc() + void release(void *ptr, size_t size) { + Memory::release(ptr); + ham_assert(m_state.allocated_size >= size); + m_state.allocated_size -= size; + } + + private: + State m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_DEVICE_INMEM_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager.h b/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager.h new file mode 100644 index 0000000000..3a6be50d44 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Manager for the log sequence number (lsn) + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_LSN_MANAGER_H +#define HAM_LSN_MANAGER_H + +#include "0root/root.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class LsnManager +{ + public: + // Constructor + LsnManager() + : m_state(1) { + } + + // Returns the next lsn + uint64_t next() { + return (m_state++); + } + + private: + friend struct LsnManagerTest; + + // the actual lsn + uint64_t m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_LSN_MANAGER_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager_test.h b/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager_test.h new file mode 100644 index 0000000000..59197a66cd --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2lsn_manager/lsn_manager_test.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Test gateway for LsnManager + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_LSN_MANAGER_TEST_H +#define HAM_LSN_MANAGER_TEST_H + +#include "0root/root.h" + +#include "2lsn_manager/lsn_manager.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct LsnManagerTest +{ + // Constructor + LsnManagerTest(LsnManager *lsn_manager) + : m_state(lsn_manager->m_state) { + } + + // Returns the current lsn + uint64_t lsn() const { + return (m_state); + } + + uint64_t &m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_LSN_MANAGER_TEST_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2page/page.cc b/plugins/Dbx_kv/src/hamsterdb/src/2page/page.cc new file mode 100644 index 0000000000..64558e9370 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2page/page.cc @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +#include "1base/error.h" +#include "1os/os.h" +#include "2page/page.h" +#include "2device/device.h" +#include "3btree/btree_node_proxy.h" + +namespace hamsterdb { + +uint64_t Page::ms_page_count_flushed = 0; + +Page::Page(Device *device, LocalDatabase *db) + : m_device(device), m_db(db), m_address(0), m_is_allocated(false), + m_is_without_header(false), m_is_dirty(false), m_cursor_list(0), + m_node_proxy(0), m_data(0) +{ + memset(&m_prev[0], 0, sizeof(m_prev)); + memset(&m_next[0], 0, sizeof(m_next)); +} + +Page::~Page() +{ + ham_assert(m_cursor_list == 0); + +#ifdef HAM_ENABLE_HELGRIND + // safely unlock the mutex + m_mutex.try_lock(); +#endif + m_mutex.unlock(); + + if (m_node_proxy) { + delete m_node_proxy; + m_node_proxy = 0; + } + + if (m_data != 0) + m_device->free_page(this); +} + +void +Page::alloc(uint32_t type, uint32_t flags) +{ + m_device->alloc_page(this); + + if (flags & kInitializeWithZeroes) { + size_t page_size = m_device->page_size(); + memset(get_raw_payload(), 0, page_size); + } + + if (type) + set_type(type); +} + +void +Page::fetch(uint64_t address) +{ + m_device->read_page(this, address); + set_address(address); +} + +void +Page::flush() +{ + if (is_dirty()) { + m_device->write_page(this); + set_dirty(false); + ms_page_count_flushed++; + } +} + +void +Page::free_buffer() +{ + if (m_node_proxy) { + delete m_node_proxy; + m_node_proxy = 0; + } + + if (m_is_allocated) + Memory::release(m_data); + m_data = 0; +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2page/page.h b/plugins/Dbx_kv/src/hamsterdb/src/2page/page.h new file mode 100644 index 0000000000..f68edc474b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2page/page.h @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: strong + * @thread_safe: no + */ + +#ifndef HAM_PAGE_H +#define HAM_PAGE_H + +#include <string.h> +#include <boost/atomic.hpp> + +#include "1base/error.h" +#include "1base/spinlock.h" +#include "1mem/mem.h" + +namespace hamsterdb { + +class Device; +class BtreeCursor; +class BtreeNodeProxy; +class LocalDatabase; + +#include "1base/packstart.h" + +/* + * This header is only available if the (non-persistent) flag + * kNpersNoHeader is not set! Blob pages do not have this header. + */ +typedef HAM_PACK_0 struct HAM_PACK_1 PPageHeader { + // flags of this page - currently only used for the Page::kType* codes + uint32_t flags; + + // reserved + uint32_t reserved; + + // the lsn of the last operation + uint64_t lsn; + + // the persistent data blob + uint8_t payload[1]; + +} HAM_PACK_2 PPageHeader; + +#include "1base/packstop.h" + +#include "1base/packstart.h" + +/* + * A union combining the page header and a pointer to the raw page data. + * + * This structure definition is present outside of @ref Page scope + * to allow compile-time OFFSETOF macros to correctly judge the size, + * depending on platform and compiler settings. + */ +typedef HAM_PACK_0 union HAM_PACK_1 PPageData { + // the persistent header + struct PPageHeader header; + + // a char pointer to the allocated storage on disk + uint8_t payload[1]; + +} HAM_PACK_2 PPageData; + +#include "1base/packstop.h" + +/* + * The Page class + * + * Each Page instance is a node in several linked lists. + * In order to avoid multiple memory allocations, the previous/next pointers + * are part of the Page class (m_prev and m_next). Both fields are arrays + * of pointers and can be used i.e. with m_prev[Page::kListBucket] etc. + * (or with the methods defined below). + */ +class Page { + public: + // Misc. enums + enum { + // sizeof the persistent page header + kSizeofPersistentHeader = sizeof(PPageHeader) - 1, + + // instruct Page::alloc() to reset the page with zeroes + kInitializeWithZeroes, + }; + + // The various linked lists (indices in m_prev, m_next) + enum { + // list of all cached pages + kListCache = 0, + + // list of all pages in a changeset + kListChangeset = 1, + + // a bucket in the hash table of the cache + kListBucket = 2, + + // array limit + kListMax = 3 + }; + + // non-persistent page flags + enum { + // page->m_data was allocated with malloc, not mmap + kNpersMalloc = 1, + + // page has no header (i.e. it's part of a large blob) + kNpersNoHeader = 2 + }; + + // Page types + // + // When large BLOBs span multiple pages, only their initial page + // will have a valid type code; subsequent pages of this blog will store + // the data as-is, so as to provide one continuous storage space + enum { + // unidentified db page type + kTypeUnknown = 0x00000000, + + // the header page: this is the first page in the environment (offset 0) + kTypeHeader = 0x10000000, + + // a B+tree root page + kTypeBroot = 0x20000000, + + // a B+tree node page + kTypeBindex = 0x30000000, + + // a page storing the state of the PageManager + kTypePageManager = 0x40000000, + + // a page which stores blobs + kTypeBlob = 0x50000000 + }; + + // Default constructor + Page(Device *device, LocalDatabase *db = 0); + + // Destructor - releases allocated memory and resources, but neither + // flushes dirty pages to disk nor moves them to the freelist! + // Asserts that no cursors are attached. + ~Page(); + + // Returns the size of the usable persistent payload of a page + // (page_size minus the overhead of the page header) + static uint32_t usable_page_size(uint32_t raw_page_size) { + return (raw_page_size - Page::kSizeofPersistentHeader); + } + + + // Returns the database which manages this page; can be NULL if this + // page belongs to the Environment (i.e. for freelist-pages) + LocalDatabase *get_db() { + return (m_db); + } + + // Sets the database to which this Page belongs + void set_db(LocalDatabase *db) { + m_db = db; + } + + // Returns the spinlock + Spinlock &mutex() { + return (m_mutex); + } + + // Returns the device + Device *device() { + return (m_device); + } + + // Returns true if this is the header page of the Environment + bool is_header() const { + return (m_address == 0); + } + + // Returns the address of this page + uint64_t get_address() const { + return (m_address); + } + + // Sets the address of this page + void set_address(uint64_t address) { + m_address = address; + } + + // Returns true if this page is dirty (and needs to be flushed to disk) + bool is_dirty() const { + return (m_is_dirty); + } + + // Sets this page dirty/not dirty + void set_dirty(bool dirty) { + m_is_dirty = dirty; + } + + // Returns true if the page's buffer was allocated with malloc + bool is_allocated() const { + return (m_is_allocated); + } + + // Returns true if the page has no persistent header + bool is_without_header() const { + return (m_is_without_header); + } + + // Sets a flag whether the page has no persistent header + void set_without_header(bool without_header) { + m_is_without_header = without_header; + } + + // Assign a buffer which was allocated with malloc() + void assign_allocated_buffer(void *buffer, uint64_t address) { + m_data = (PPageData *)buffer; + m_is_allocated = true; + m_address = address; + } + + // Assign a buffer from mmapped storage + void assign_mapped_buffer(void *buffer, uint64_t address) { + m_data = (PPageData *)buffer; + m_is_allocated = false; + m_address = address; + } + + // Free resources associated with the buffer + void free_buffer(); + + // Returns the linked list of coupled cursors (can be NULL) + BtreeCursor *cursor_list() { + return (m_cursor_list); + } + + // Sets the (head of the) linked list of cursors + void set_cursor_list(BtreeCursor *cursor) { + m_cursor_list = cursor; + } + + // Returns the page's type (kType*) + uint32_t get_type() const { + return (m_data->header.flags); + } + + // Sets the page's type (kType*) + void set_type(uint32_t type) { + m_data->header.flags = type; + } + + // Returns the lsn of the last modification + uint64_t get_lsn() const { + return (m_data->header.lsn); + } + + // Sets the lsn of the last modification + void set_lsn(uint64_t lsn) { + m_data->header.lsn = lsn; + } + + // Sets the pointer to the persistent data + void set_data(PPageData *data) { + m_data = data; + } + + // Returns the pointer to the persistent data + PPageData *get_data() { + return (m_data); + } + + // Returns the persistent payload (after the header!) + uint8_t *get_payload() { + return (m_data->header.payload); + } + + // Returns the persistent payload (after the header!) + const uint8_t *get_payload() const { + return (m_data->header.payload); + } + + // Returns the persistent payload (including the header!) + uint8_t *get_raw_payload() { + return (m_data->payload); + } + + // Returns the persistent payload (including the header!) + const uint8_t *get_raw_payload() const { + return (m_data->payload); + } + + // Allocates a new page from the device + // |flags|: either 0 or kInitializeWithZeroes + void alloc(uint32_t type, uint32_t flags = 0); + + // Reads a page from the device + void fetch(uint64_t address); + + // Writes the page to the device + void flush(); + + // Returns true if this page is in a linked list + bool is_in_list(Page *list_head, int list) { + if (get_next(list) != 0) + return (true); + if (get_previous(list) != 0) + return (true); + return (list_head == this); + } + + // Inserts this page at the beginning of a list and returns the + // new head of the list + Page *list_insert(Page *list_head, int list) { + set_next(list, 0); + set_previous(list, 0); + + if (!list_head) + return (this); + + set_next(list, list_head); + list_head->set_previous(list, this); + return (this); + } + + // Removes this page from a list and returns the new head of the list + Page *list_remove(Page *list_head, int list) { + Page *n, *p; + + if (this == list_head) { + n = get_next(list); + if (n) + n->set_previous(list, 0); + set_next(list, 0); + set_previous(list, 0); + return (n); + } + + n = get_next(list); + p = get_previous(list); + if (p) + p->set_next(list, n); + if (n) + n->set_previous(list, p); + set_next(list, 0); + set_previous(list, 0); + return (list_head); + } + + // Returns the next page in a linked list + Page *get_next(int list) { + return (m_next[list]); + } + + // Returns the previous page of a linked list + Page *get_previous(int list) { + return (m_prev[list]); + } + + // Returns the cached BtreeNodeProxy + BtreeNodeProxy *get_node_proxy() { + return (m_node_proxy); + } + + // Sets the cached BtreeNodeProxy + void set_node_proxy(BtreeNodeProxy *proxy) { + m_node_proxy = proxy; + } + + // tracks number of flushed pages + static uint64_t ms_page_count_flushed; + + private: + friend class PageCollection; + + // Sets the previous page of a linked list + void set_previous(int list, Page *other) { + m_prev[list] = other; + } + + // Sets the next page in a linked list + void set_next(int list, Page *other) { + m_next[list] = other; + } + + // the Device for allocating storage + Device *m_device; + + // the Database handle (can be NULL) + LocalDatabase *m_db; + + // The spinlock is locked if the page is in use or written to disk + Spinlock m_mutex; + + // address of this page + uint64_t m_address; + + // Page buffer was allocated with malloc() (if not then it was mapped + // with mmap) + bool m_is_allocated; + + // Page does not have a persistent header + bool m_is_without_header; + + // is this page dirty and needs to be flushed to disk? + bool m_is_dirty; + + // linked list of all cursors which point to that page + BtreeCursor *m_cursor_list; + + // linked lists of pages - see comments above + Page *m_prev[Page::kListMax]; + Page *m_next[Page::kListMax]; + + // the cached BtreeNodeProxy object + BtreeNodeProxy *m_node_proxy; + + // the persistent data of this page + PPageData *m_data; +}; + +} // namespace hamsterdb + +#endif /* HAM_PAGE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2page/page_collection.h b/plugins/Dbx_kv/src/hamsterdb/src/2page/page_collection.h new file mode 100644 index 0000000000..b396c78165 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2page/page_collection.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: strong + * @thread_safe: no + */ + +#ifndef HAM_PAGE_COLLECTION_H +#define HAM_PAGE_COLLECTION_H + +#include <string.h> + +#include <boost/atomic.hpp> + +#include "1mem/mem.h" +#include "2page/page.h" + +namespace hamsterdb { + +/* + * The PageCollection class + */ +class PageCollection { + public: + // Default constructor + PageCollection(int list_id) + : m_head(0), m_tail(0), m_size(0), m_id(list_id) { + } + + // Destructor + ~PageCollection() { + clear(); + } + + bool is_empty() const { + return (m_size == 0); + } + + int size() const { + return (m_size); + } + + // Atomically applies the |visitor()| to each page + template<typename Visitor> + void for_each(Visitor &visitor) { + for (Page *p = m_head; p != 0; p = p->get_next(m_id)) { + if (!visitor(p)) + break; + } + } + + // Atomically applies the |visitor()| to each page; starts at the tail + template<typename Visitor> + void for_each_reverse(Visitor &visitor) { + for (Page *p = m_tail; p != 0; p = p->get_previous(m_id)) { + if (!visitor(p)) + break; + } + } + + // Same as |for_each()|, but removes the page if |visitor()| returns true + template<typename Visitor> + void extract(Visitor &visitor) { + Page *page = m_head; + while (page) { + Page *next = page->get_next(m_id); + if (visitor(page)) { + del_impl(page); + } + page = next; + } + } + + // Clears the collection. + void clear() { + Page *page = m_head; + while (page) { + Page *next = page->get_next(m_id); + del_impl(page); + page = next; + } + + ham_assert(m_head == 0); + ham_assert(m_tail == 0); + ham_assert(m_size == 0); + } + + // Returns the head + Page *head() const { + return (m_head); + } + + // Returns the tail + Page *tail() const { + return (m_tail); + } + + // Returns a page from the collection + Page *get(uint64_t address) const { + for (Page *p = m_head; p != 0; p = p->get_next(m_id)) { + if (p->get_address() == address) + return (p); + } + return (0); + } + + // Removes a page from the collection. Returns true if the page was removed, + // otherwise false (if the page was not in the list) + bool del(Page *page) { + if (has(page)) { + del_impl(page); + return (true); + } + return (false); + } + + // Adds a new page at the head of the list. Returns true if the page was + // added, otherwise false (that's the case if the page is already part of + // the list) + bool put(Page *page) { + if (!has(page)) { + m_head = page->list_insert(m_head, m_id); + if (!m_tail) + m_tail = page; + ++m_size; + return (true); + } + return (false); + } + + // Returns true if a page with the |address| is already stored. + bool has(uint64_t address) const { + return (get(address) != 0); + } + + // Returns true if the |page| is already stored. This is much faster + // than has(uint64_t address). + bool has(Page *page) const { + return (page->is_in_list(m_head, m_id)); + } + + private: + void del_impl(Page *page) { + // First update the tail because Page::list_remove() will change the + // pointers! + if (m_tail == page) + m_tail = page->get_previous(m_id); + m_head = page->list_remove(m_head, m_id); + ham_assert(m_size > 0); + --m_size; + } + + // The head of the linked list + Page *m_head; + + // The tail of the linked list + Page *m_tail; + + // Number of elements in the list + int m_size; + + // The list ID + int m_id; +}; + +} // namespace hamsterdb + +#endif /* HAM_PAGE_COLLECTION_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.am b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.am new file mode 100644 index 0000000000..b5c5c881f4 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.am @@ -0,0 +1,15 @@ + +AM_CPPFLAGS = -DHAM_ENABLE_REMOTE -I$(top_builddir)/include + +# INCLUDES = + +noinst_LTLIBRARIES = libprotocol.la + +nodist_libprotocol_la_SOURCES = messages.pb.cc +libprotocol_la_SOURCES = protocol.h +libprotocol_la_LIBADD = -lprotobuf + +EXTRA_DIST = messages.proto +messages.pb.cc proto: $(srcdir)/messages.proto + protoc $(srcdir)/messages.proto --cpp_out=. + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.in b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.in new file mode 100644 index 0000000000..e198a11d7d --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/Makefile.in @@ -0,0 +1,627 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/2protobuf +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/ax_cxx_gcc_abi_demangle.m4 \ + $(top_srcdir)/m4/boost.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libprotocol_la_DEPENDENCIES = +am_libprotocol_la_OBJECTS = +nodist_libprotocol_la_OBJECTS = messages.pb.lo +libprotocol_la_OBJECTS = $(am_libprotocol_la_OBJECTS) \ + $(nodist_libprotocol_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libprotocol_la_SOURCES) $(nodist_libprotocol_la_SOURCES) +DIST_SOURCES = $(libprotocol_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LDFLAGS = @BOOST_CHRONO_LDFLAGS@ +BOOST_CHRONO_LDPATH = @BOOST_CHRONO_LDPATH@ +BOOST_CHRONO_LIBS = @BOOST_CHRONO_LIBS@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@ +BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@ +BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@ +BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@ +BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@ +BOOST_THREAD_LDFLAGS = @BOOST_THREAD_LDFLAGS@ +BOOST_THREAD_LDPATH = @BOOST_THREAD_LDPATH@ +BOOST_THREAD_LIBS = @BOOST_THREAD_LIBS@ +BOOST_THREAD_WIN32_LDFLAGS = @BOOST_THREAD_WIN32_LDFLAGS@ +BOOST_THREAD_WIN32_LDPATH = @BOOST_THREAD_WIN32_LDPATH@ +BOOST_THREAD_WIN32_LIBS = @BOOST_THREAD_WIN32_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JDK_INCLUDE = @JDK_INCLUDE@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = -DHAM_ENABLE_REMOTE -I$(top_builddir)/include + +# INCLUDES = +noinst_LTLIBRARIES = libprotocol.la +nodist_libprotocol_la_SOURCES = messages.pb.cc +libprotocol_la_SOURCES = protocol.h +libprotocol_la_LIBADD = -lprotobuf +EXTRA_DIST = messages.proto +all: all-am + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/2protobuf/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/2protobuf/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libprotocol.la: $(libprotocol_la_OBJECTS) $(libprotocol_la_DEPENDENCIES) $(EXTRA_libprotocol_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libprotocol_la_OBJECTS) $(libprotocol_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/messages.pb.Plo@am__quote@ + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +messages.pb.cc proto: $(srcdir)/messages.proto + protoc $(srcdir)/messages.proto --cpp_out=. + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/messages.proto b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/messages.proto new file mode 100644 index 0000000000..f8ec8fdcb6 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/messages.proto @@ -0,0 +1,457 @@ + + +package hamsterdb; + +option optimize_for = LITE_RUNTIME; + +message ProtoWrapper { + enum Type { + CONNECT_REQUEST = 10; + CONNECT_REPLY = 11; + DISCONNECT_REQUEST = 12; + DISCONNECT_REPLY = 13; + ENV_RENAME_REQUEST = 20; + ENV_RENAME_REPLY = 21; + ENV_GET_PARAMETERS_REQUEST = 30; + ENV_GET_PARAMETERS_REPLY = 31; + ENV_GET_DATABASE_NAMES_REQUEST = 40; + ENV_GET_DATABASE_NAMES_REPLY = 41; + ENV_FLUSH_REQUEST = 50; + ENV_FLUSH_REPLY = 51; + ENV_CREATE_DB_REQUEST = 60; + ENV_CREATE_DB_REPLY = 61; + ENV_OPEN_DB_REQUEST = 70; + ENV_OPEN_DB_REPLY = 71; + ENV_ERASE_DB_REQUEST = 80; + ENV_ERASE_DB_REPLY = 81; + DB_CLOSE_REQUEST = 90; + DB_CLOSE_REPLY = 91; + DB_GET_PARAMETERS_REQUEST = 100; + DB_GET_PARAMETERS_REPLY = 101; + // DB_FLUSH_REQUEST = 110; + // DB_FLUSH_REPLY = 111; + TXN_BEGIN_REQUEST = 120; + TXN_BEGIN_REPLY = 121; + TXN_COMMIT_REQUEST = 130; + TXN_COMMIT_REPLY = 131; + TXN_ABORT_REQUEST = 140; + TXN_ABORT_REPLY = 141; + DB_CHECK_INTEGRITY_REQUEST = 150; + DB_CHECK_INTEGRITY_REPLY = 151; + DB_GET_KEY_COUNT_REQUEST = 160; + DB_GET_KEY_COUNT_REPLY = 161; + DB_INSERT_REQUEST = 170; + DB_INSERT_REPLY = 171; + DB_ERASE_REQUEST = 180; + DB_ERASE_REPLY = 181; + DB_FIND_REQUEST = 190; + DB_FIND_REPLY = 191; + CURSOR_CREATE_REQUEST = 200; + CURSOR_CREATE_REPLY = 201; + CURSOR_CLONE_REQUEST = 210; + CURSOR_CLONE_REPLY = 211; + CURSOR_CLOSE_REQUEST = 220; + CURSOR_CLOSE_REPLY = 221; + CURSOR_INSERT_REQUEST = 230; + CURSOR_INSERT_REPLY = 231; + CURSOR_ERASE_REQUEST = 240; + CURSOR_ERASE_REPLY = 241; + CURSOR_GET_RECORD_COUNT_REQUEST = 260; + CURSOR_GET_RECORD_COUNT_REPLY = 261; + CURSOR_GET_DUPLICATE_POSITION_REQUEST = 262; + CURSOR_GET_DUPLICATE_POSITION_REPLY = 263; + CURSOR_GET_RECORD_SIZE_REQUEST = 264; + CURSOR_GET_RECORD_SIZE_REPLY = 265; + CURSOR_OVERWRITE_REQUEST = 270; + CURSOR_OVERWRITE_REPLY = 271; + CURSOR_MOVE_REQUEST = 280; + CURSOR_MOVE_REPLY = 281; + } + + required Type type = 1; + + optional ConnectRequest connect_request = 10; + optional ConnectReply connect_reply = 11; + optional DisconnectRequest disconnect_request = 12; + optional DisconnectReply disconnect_reply = 13; + optional EnvRenameRequest env_rename_request = 20; + optional EnvRenameReply env_rename_reply = 21; + optional EnvGetParametersRequest env_get_parameters_request = 30; + optional EnvGetParametersReply env_get_parameters_reply = 31; + optional EnvGetDatabaseNamesRequest env_get_database_names_request = 40; + optional EnvGetDatabaseNamesReply env_get_database_names_reply = 41; + optional EnvFlushRequest env_flush_request = 50; + optional EnvFlushReply env_flush_reply = 51; + optional EnvCreateDbRequest env_create_db_request = 60; + optional EnvCreateDbReply env_create_db_reply = 61; + optional EnvOpenDbRequest env_open_db_request = 70; + optional EnvOpenDbReply env_open_db_reply = 71; + optional EnvEraseDbRequest env_erase_db_request = 80; + optional EnvEraseDbReply env_erase_db_reply = 81; + optional DbCloseRequest db_close_request = 90; + optional DbCloseReply db_close_reply = 91; + optional DbGetParametersRequest db_get_parameters_request = 100; + optional DbGetParametersReply db_get_parameters_reply = 101; + optional TxnBeginRequest txn_begin_request = 120; + optional TxnBeginReply txn_begin_reply = 121; + optional TxnCommitRequest txn_commit_request = 130; + optional TxnCommitReply txn_commit_reply = 131; + optional TxnAbortRequest txn_abort_request = 140; + optional TxnAbortReply txn_abort_reply = 141; + optional DbCheckIntegrityRequest db_check_integrity_request = 150; + optional DbCheckIntegrityReply db_check_integrity_reply = 151; + optional DbCountRequest db_count_request = 160; + optional DbCountReply db_count_reply = 161; + optional DbInsertRequest db_insert_request = 170; + optional DbInsertReply db_insert_reply = 171; + optional DbEraseRequest db_erase_request = 180; + optional DbEraseReply db_erase_reply = 181; + optional DbFindRequest db_find_request = 190; + optional DbFindReply db_find_reply = 191; + optional CursorCreateRequest cursor_create_request = 200; + optional CursorCreateReply cursor_create_reply = 201; + optional CursorCloneRequest cursor_clone_request = 210; + optional CursorCloneReply cursor_clone_reply = 211; + optional CursorCloseRequest cursor_close_request = 220; + optional CursorCloseReply cursor_close_reply = 221; + optional CursorInsertRequest cursor_insert_request = 230; + optional CursorInsertReply cursor_insert_reply = 231; + optional CursorEraseRequest cursor_erase_request = 240; + optional CursorEraseReply cursor_erase_reply = 241; + optional CursorGetRecordCountRequest cursor_get_record_count_request = 260; + optional CursorGetRecordCountReply cursor_get_record_count_reply = 261; + optional CursorGetDuplicatePositionRequest cursor_get_duplicate_position_request = 262; + optional CursorGetDuplicatePositionReply cursor_get_duplicate_position_reply = 263; + optional CursorGetRecordSizeRequest cursor_get_record_size_request = 264; + optional CursorGetRecordSizeReply cursor_get_record_size_reply = 265; + optional CursorOverwriteRequest cursor_overwrite_request = 270; + optional CursorOverwriteReply cursor_overwrite_reply = 271; + optional CursorMoveRequest cursor_move_request = 280; + optional CursorMoveReply cursor_move_reply = 281; +} + +message ConnectRequest { + required string path = 1; +} + +message ConnectReply { + required sint32 status = 1; + optional uint32 env_flags = 2; + optional uint64 env_handle = 3; +} + +message DisconnectRequest { + required uint64 env_handle = 1; +} + +message DisconnectReply { + required sint32 status = 1; +} + +message EnvGetParametersRequest { + required uint64 env_handle = 1; + repeated uint32 names = 2; +} + +message EnvGetParametersReply { + required sint32 status = 1; + optional uint32 cache_size = 2; + optional uint32 page_size = 3; + optional uint32 max_env_databases = 4; + optional uint32 flags = 5; + optional uint32 filemode = 6; + optional string filename = 7; +}; + +message EnvGetDatabaseNamesRequest { + required uint64 env_handle = 1; +} + +message EnvGetDatabaseNamesReply { + required sint32 status = 1; + repeated uint32 names = 2; +} + +message EnvRenameRequest { + required uint64 env_handle = 1; + required uint32 oldname = 2; + required uint32 newname = 3; + required uint32 flags = 4; +} + +message EnvRenameReply { + required sint32 status = 1; +}; + +message EnvFlushRequest { + required uint64 env_handle = 1; + required uint32 flags = 2; +} + +message EnvFlushReply { + required sint32 status = 1; +}; + +message EnvCreateDbRequest { + required uint64 env_handle = 1; + required uint32 dbname = 2; + required uint32 flags = 3; + repeated uint32 param_names = 4; + repeated uint64 param_values = 5; +} + +message EnvCreateDbReply { + required sint32 status = 1; + optional uint64 db_handle = 2; + optional uint32 db_flags = 3; +}; + +message EnvOpenDbRequest { + required uint64 env_handle = 1; + required uint32 dbname = 2; + required uint32 flags = 3; + repeated uint32 param_names = 4; + repeated uint64 param_values = 5; +} + +message EnvOpenDbReply { + required sint32 status = 1; + optional uint64 db_handle = 2; + optional uint32 db_flags = 3; +}; + +message EnvEraseDbRequest { + required uint64 env_handle = 1; + required uint32 name = 2; + required uint32 flags = 3; +} + +message EnvEraseDbReply { + required sint32 status = 1; +}; + +message DbCloseRequest { + required uint64 db_handle = 1; + required uint32 flags = 2; +} + +message DbCloseReply { + required sint32 status = 1; +}; + +message DbGetParametersRequest { + required uint64 db_handle = 1; + repeated uint32 names = 2; +} + +message DbGetParametersReply { + required sint32 status = 1; + optional uint32 max_env_databases = 2; + optional uint32 flags = 3; + optional uint32 key_size = 4; + optional uint32 dbname = 5; + optional uint32 keys_per_page = 6; + optional uint32 key_type = 7; + optional uint32 record_size = 8; +}; + +message TxnBeginRequest { + required uint64 env_handle = 1; + required uint32 flags = 2; + optional string name = 3; +} + +message TxnBeginReply { + required sint32 status = 1; + required uint64 txn_handle = 2; +}; + +message TxnCommitRequest { + required uint64 txn_handle = 1; + required uint32 flags = 2; +} + +message TxnCommitReply { + required sint32 status = 1; +}; + +message TxnAbortRequest { + required uint64 txn_handle = 1; + required uint32 flags = 2; +} + +message TxnAbortReply { + required sint32 status = 1; +}; + +message DbCheckIntegrityRequest { + required uint64 db_handle = 1; + required uint32 flags = 2; +} + +message DbCheckIntegrityReply { + required sint32 status = 1; +}; + +message DbCountRequest { + required uint64 db_handle = 1; + required uint64 txn_handle = 2; + required bool distinct = 3; +}; + +message DbCountReply { + required sint32 status = 1; + required uint64 keycount = 2; +}; + +message Key { + optional bytes data = 1; + required uint32 flags = 2; + required uint32 intflags = 3; +} + +message Record { + optional bytes data = 1; + required uint32 flags = 2; + required uint32 partial_offset = 3; + required uint32 partial_size = 4; +} + +message DbInsertRequest { + required uint64 db_handle = 1; + required uint64 txn_handle = 2; + optional Key key = 3; + optional Record record = 4; + required uint32 flags = 5; +}; + +message DbInsertReply { + required sint32 status = 1; + optional Key key = 2; +}; + +message DbEraseRequest { + required uint64 db_handle = 1; + required uint64 txn_handle = 2; + required Key key = 3; + required uint32 flags = 4; +}; + +message DbEraseReply { + required sint32 status = 1; +}; + +message DbFindRequest { + required uint64 db_handle = 1; + required uint64 txn_handle = 2; + required uint64 cursor_handle = 3; + required Key key = 4; + optional Record record = 5; + required uint32 flags = 6; +}; + +message DbFindReply { + required sint32 status = 1; + required Record record = 2; + optional Key key = 3; +}; + +message CursorCreateRequest { + required uint64 db_handle = 1; + required uint64 txn_handle = 2; + required uint32 flags = 3; +}; + +message CursorCreateReply { + required sint32 status = 1; + required uint64 cursor_handle = 2; +}; + +message CursorCloneRequest { + required uint64 cursor_handle = 1; +}; + +message CursorCloneReply { + required sint32 status = 1; + required uint64 cursor_handle = 2; +}; + +message CursorCloseRequest { + required uint64 cursor_handle = 1; +}; + +message CursorCloseReply { + required sint32 status = 1; +}; + +message CursorInsertRequest { + required uint64 cursor_handle = 1; + optional Key key = 2; + optional Record record = 3; + required uint32 flags = 4; +}; + +message CursorInsertReply { + required sint32 status = 1; + optional Key key = 2; +}; + +message CursorEraseRequest { + required uint64 cursor_handle = 1; + required uint32 flags = 2; +}; + +message CursorEraseReply { + required sint32 status = 1; +}; + +message CursorGetRecordCountRequest { + required uint64 cursor_handle = 1; + required uint32 flags = 2; +}; + +message CursorGetRecordCountReply { + required sint32 status = 1; + required uint32 count = 2; +}; + +message CursorGetRecordSizeRequest { + required uint64 cursor_handle = 1; +}; + +message CursorGetRecordSizeReply { + required sint32 status = 1; + required uint64 size = 2; +}; + +message CursorGetDuplicatePositionRequest { + required uint64 cursor_handle = 1; +}; + +message CursorGetDuplicatePositionReply { + required sint32 status = 1; + required uint32 position = 2; +}; + +message CursorOverwriteRequest { + required uint64 cursor_handle = 1; + required Record record = 2; + required uint32 flags = 3; +}; + +message CursorOverwriteReply { + required sint32 status = 1; +}; + +message CursorMoveRequest { + required uint64 cursor_handle = 1; + optional Key key = 2; + optional Record record = 3; + required uint32 flags = 4; +}; + +message CursorMoveReply { + required sint32 status = 1; + optional Key key = 2; + optional Record record = 3; +}; diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/protocol.h b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/protocol.h new file mode 100644 index 0000000000..8a2ab9d49f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protobuf/protocol.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Abstraction layer for the remote protocol + * + * @exception_safe: no + * @thread_safe: no + */ + +#ifndef HAM_PROTOCOL_H +#define HAM_PROTOCOL_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1mem/mem.h" +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "2protobuf/messages.pb.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +/** a magic and version indicator for the remote protocol */ +#define HAM_TRANSFER_MAGIC_V1 (('h'<<24)|('a'<<16)|('m'<<8)|'1') + +/** + * the Protocol class maps a single message that is exchanged between + * client and server + */ +class Protocol : public hamsterdb::ProtoWrapper +{ + public: + Protocol() { } + + /** constructor - assigns a type */ + Protocol(hamsterdb::ProtoWrapper_Type type) { + set_type(type); + } + + /** helper function which copies a ham_key_t into a ProtoBuf key */ + static void assign_key(hamsterdb::Key *protokey, ham_key_t *hamkey, + bool deep_copy = true) { + if (deep_copy) + protokey->set_data(hamkey->data, hamkey->size); + protokey->set_flags(hamkey->flags); + protokey->set_intflags(hamkey->_flags); + } + + /** helper function which copies a ham_record_t into a ProtoBuf record */ + static void assign_record(hamsterdb::Record *protorec, + ham_record_t *hamrec, bool deep_copy = true) { + if (deep_copy) + protorec->set_data(hamrec->data, hamrec->size); + protorec->set_flags(hamrec->flags); + protorec->set_partial_offset(hamrec->partial_offset); + protorec->set_partial_size(hamrec->partial_size); + } + + /** + * Factory function; creates a new Protocol structure from a serialized + * buffer + */ + static Protocol *unpack(const uint8_t *buf, uint32_t size) { + if (*(uint32_t *)&buf[0] != HAM_TRANSFER_MAGIC_V1) { + ham_trace(("invalid protocol version")); + return (0); + } + + Protocol *p = new Protocol; + if (!p->ParseFromArray(buf + 8, size - 8)) { + delete p; + return (0); + } + return (p); + } + + /* + * Packs the Protocol structure into a memory buffer and returns + * a pointer to the buffer and the buffer size + */ + bool pack(uint8_t **data, uint32_t *size) { + uint32_t packed_size = ByteSize(); + /* we need 8 more bytes for magic and size */ + uint8_t *p = Memory::allocate<uint8_t>(packed_size + 8); + if (!p) + return (false); + + /* write the magic and the payload size of the packed structure */ + *(uint32_t *)&p[0] = HAM_TRANSFER_MAGIC_V1; + *(uint32_t *)&p[4] = packed_size; + + /* now write the packed structure */ + if (!SerializeToArray(&p[8], packed_size)) { + Memory::release(p); + return (false); + } + + *data = p; + *size = packed_size + 8; + return (true); + } + + /* + * Packs the Protocol structure into a ByteArray + */ + bool pack(ByteArray *barray) { + uint32_t packed_size = ByteSize(); + /* we need 8 more bytes for magic and size */ + uint8_t *p = (uint8_t *)barray->resize(packed_size + 8); + if (!p) + return (false); + + /* write the magic and the payload size of the packed structure */ + *(uint32_t *)&p[0] = HAM_TRANSFER_MAGIC_V1; + *(uint32_t *)&p[4] = packed_size; + + /* now write the packed structure */ + return (SerializeToArray(&p[8], packed_size)); + } + + /** + * shutdown/free globally allocated memory + */ + static void shutdown() { + google::protobuf::ShutdownProtobufLibrary(); + } +}; + +#endif /* HAM_PROTOCOL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.am b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.am new file mode 100644 index 0000000000..cf5a3fb3ec --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.am @@ -0,0 +1,5 @@ + +EXTRA_DIST = messages.h messages.proto + +gen proto: + cat messages.proto | ../../bin/genserializer.pl > messages.h diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.in b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.in new file mode 100644 index 0000000000..da966a01d3 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/Makefile.in @@ -0,0 +1,451 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/2protoserde +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/ax_cxx_gcc_abi_demangle.m4 \ + $(top_srcdir)/m4/boost.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LDFLAGS = @BOOST_CHRONO_LDFLAGS@ +BOOST_CHRONO_LDPATH = @BOOST_CHRONO_LDPATH@ +BOOST_CHRONO_LIBS = @BOOST_CHRONO_LIBS@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_FILESYSTEM_LDFLAGS = @BOOST_FILESYSTEM_LDFLAGS@ +BOOST_FILESYSTEM_LDPATH = @BOOST_FILESYSTEM_LDPATH@ +BOOST_FILESYSTEM_LIBS = @BOOST_FILESYSTEM_LIBS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_SYSTEM_LDFLAGS = @BOOST_SYSTEM_LDFLAGS@ +BOOST_SYSTEM_LDPATH = @BOOST_SYSTEM_LDPATH@ +BOOST_SYSTEM_LIBS = @BOOST_SYSTEM_LIBS@ +BOOST_THREAD_LDFLAGS = @BOOST_THREAD_LDFLAGS@ +BOOST_THREAD_LDPATH = @BOOST_THREAD_LDPATH@ +BOOST_THREAD_LIBS = @BOOST_THREAD_LIBS@ +BOOST_THREAD_WIN32_LDFLAGS = @BOOST_THREAD_WIN32_LDFLAGS@ +BOOST_THREAD_WIN32_LDPATH = @BOOST_THREAD_WIN32_LDPATH@ +BOOST_THREAD_WIN32_LIBS = @BOOST_THREAD_WIN32_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JDK_INCLUDE = @JDK_INCLUDE@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = messages.h messages.proto +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/2protoserde/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/2protoserde/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + + +gen proto: + cat messages.proto | ../../bin/genserializer.pl > messages.h + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.h b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.h new file mode 100644 index 0000000000..38d091dd8f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.h @@ -0,0 +1,1839 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_MESSAGES_H +#define HAM_MESSAGES_H + +#include "0root/root.h" + +#include <assert.h> + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +/** a magic and version indicator for the remote protocol */ +#define HAM_TRANSFER_MAGIC_V2 (('h'<<24)|('a'<<16)|('m'<<8)|'2') + +namespace hamsterdb { + +enum { + kTxnBeginRequest, + kTxnBeginReply, + kTxnCommitRequest, + kTxnCommitReply, + kTxnAbortRequest, + kTxnAbortReply, + kDbGetKeyCountRequest, + kDbGetKeyCountReply, + kDbInsertRequest, + kDbInsertReply, + kDbEraseRequest, + kDbEraseReply, + kDbFindRequest, + kDbFindReply, + kCursorCreateRequest, + kCursorCreateReply, + kCursorCloneRequest, + kCursorCloneReply, + kCursorCloseRequest, + kCursorCloseReply, + kCursorInsertRequest, + kCursorInsertReply, + kCursorEraseRequest, + kCursorEraseReply, + kCursorGetRecordCountRequest, + kCursorGetRecordCountReply, + kCursorGetRecordSizeRequest, + kCursorGetRecordSizeReply, + kCursorGetDuplicatePositionRequest, + kCursorGetDuplicatePositionReply, + kCursorOverwriteRequest, + kCursorOverwriteReply, + kCursorMoveRequest, + kCursorMoveReply +}; + +template<typename Ex, typename In> +struct Serialized_Base { + Ex value; + + Serialized_Base() { + clear(); + } + + Serialized_Base(const Ex &t) + : value((In)t) { + } + + operator Ex() { + return (value); + } + + void clear() { + value = (Ex)0; + } + + size_t get_size() const { + return (sizeof(In)); + } + + void serialize(unsigned char **pptr, int *psize) const { + *(In *)*pptr = (In)value; + *pptr += sizeof(In); + *psize -= sizeof(In); + assert(*psize >= 0); + } + + void deserialize(unsigned char **pptr, int *psize) { + value = (Ex) *(In *)*pptr; + *pptr += sizeof(In); + *psize -= sizeof(In); + assert(*psize >= 0); + } +}; + +struct SerializedBytes { + uint8_t *value; + uint32_t size; + + SerializedBytes() { + clear(); + } + + size_t align(size_t s) const { + if (s % 4) return (s + 4 - (s % 4)); + return (s); + } + + void clear() { + value = 0; size = 0; + } + + size_t get_size() const { + return (sizeof(uint32_t) + align(size)); // align to 32bits + } + + void serialize(unsigned char **pptr, int *psize) const { + *(uint32_t *)*pptr = size; + *pptr += sizeof(uint32_t); + *psize -= sizeof(uint32_t); + if (size) { + memcpy(*pptr, value, size); + *pptr += align(size); // align to 32bits + *psize -= align(size); + assert(*psize >= 0); + } + } + + void deserialize(unsigned char **pptr, int *psize) { + size = *(uint32_t *)*pptr; + *pptr += sizeof(uint32_t); + *psize -= sizeof(uint32_t); + if (size) { + value = *pptr; + *pptr += align(size); // align to 32bits + *psize -= align(size); + assert(*psize >= 0); + } + else + value = 0; + } +}; + +typedef Serialized_Base<bool, uint32_t> SerializedBool; +typedef Serialized_Base<uint8_t, uint32_t> SerializedUint8; +typedef Serialized_Base<uint16_t, uint32_t> SerializedUint16; +typedef Serialized_Base<uint32_t, uint32_t> SerializedUint32; +typedef Serialized_Base<int8_t, int32_t> SerializedSint8; +typedef Serialized_Base<int16_t, int32_t> SerializedSint16; +typedef Serialized_Base<int32_t, int32_t> SerializedSint32; +typedef Serialized_Base<uint64_t, uint64_t> SerializedUint64; +typedef Serialized_Base<int64_t, int64_t> SerializedSint64; + + +struct SerializedKey { + SerializedBool has_data; + SerializedBytes data; + SerializedUint32 flags; + SerializedUint32 intflags; + + SerializedKey() { + clear(); + } + + size_t get_size() const { + return ( + has_data.get_size() + + (has_data.value ? data.get_size() : 0) + + flags.get_size() + + intflags.get_size() + + 0); + } + + void clear() { + has_data = false; + data.clear(); + flags.clear(); + intflags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + has_data.serialize(pptr, psize); + if (has_data.value) data.serialize(pptr, psize); + flags.serialize(pptr, psize); + intflags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + has_data.deserialize(pptr, psize); + if (has_data.value) data.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + intflags.deserialize(pptr, psize); + } +}; + +struct SerializedRecord { + SerializedBool has_data; + SerializedBytes data; + SerializedUint32 flags; + SerializedUint32 partial_offset; + SerializedUint32 partial_size; + + SerializedRecord() { + clear(); + } + + size_t get_size() const { + return ( + has_data.get_size() + + (has_data.value ? data.get_size() : 0) + + flags.get_size() + + partial_offset.get_size() + + partial_size.get_size() + + 0); + } + + void clear() { + has_data = false; + data.clear(); + flags.clear(); + partial_offset.clear(); + partial_size.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + has_data.serialize(pptr, psize); + if (has_data.value) data.serialize(pptr, psize); + flags.serialize(pptr, psize); + partial_offset.serialize(pptr, psize); + partial_size.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + has_data.deserialize(pptr, psize); + if (has_data.value) data.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + partial_offset.deserialize(pptr, psize); + partial_size.deserialize(pptr, psize); + } +}; + +struct SerializedConnectRequest { + SerializedBytes path; + + SerializedConnectRequest() { + clear(); + } + + size_t get_size() const { + return ( + path.get_size() + + 0); + } + + void clear() { + path.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + path.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + path.deserialize(pptr, psize); + } +}; + +struct SerializedConnectReply { + SerializedSint32 status; + SerializedUint32 env_flags; + SerializedUint64 env_handle; + + SerializedConnectReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + env_flags.get_size() + + env_handle.get_size() + + 0); + } + + void clear() { + status.clear(); + env_flags.clear(); + env_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + env_flags.serialize(pptr, psize); + env_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + env_flags.deserialize(pptr, psize); + env_handle.deserialize(pptr, psize); + } +}; + +struct SerializedTxnBeginRequest { + SerializedUint64 env_handle; + SerializedUint32 flags; + SerializedBytes name; + + SerializedTxnBeginRequest() { + clear(); + } + + size_t get_size() const { + return ( + env_handle.get_size() + + flags.get_size() + + name.get_size() + + 0); + } + + void clear() { + env_handle.clear(); + flags.clear(); + name.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + env_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + name.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + env_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + name.deserialize(pptr, psize); + } +}; + +struct SerializedTxnBeginReply { + SerializedSint32 status; + SerializedUint64 txn_handle; + + SerializedTxnBeginReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + txn_handle.get_size() + + 0); + } + + void clear() { + status.clear(); + txn_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + } +}; + +struct SerializedTxnCommitRequest { + SerializedUint64 txn_handle; + SerializedUint32 flags; + + SerializedTxnCommitRequest() { + clear(); + } + + size_t get_size() const { + return ( + txn_handle.get_size() + + flags.get_size() + + 0); + } + + void clear() { + txn_handle.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + txn_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + txn_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedTxnCommitReply { + SerializedSint32 status; + + SerializedTxnCommitReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedTxnAbortRequest { + SerializedUint64 txn_handle; + SerializedUint32 flags; + + SerializedTxnAbortRequest() { + clear(); + } + + size_t get_size() const { + return ( + txn_handle.get_size() + + flags.get_size() + + 0); + } + + void clear() { + txn_handle.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + txn_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + txn_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedTxnAbortReply { + SerializedSint32 status; + + SerializedTxnAbortReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedDbGetKeyCountRequest { + SerializedUint64 db_handle; + SerializedUint64 txn_handle; + SerializedBool distinct; + + SerializedDbGetKeyCountRequest() { + clear(); + } + + size_t get_size() const { + return ( + db_handle.get_size() + + txn_handle.get_size() + + distinct.get_size() + + 0); + } + + void clear() { + db_handle.clear(); + txn_handle.clear(); + distinct.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + db_handle.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + distinct.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + db_handle.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + distinct.deserialize(pptr, psize); + } +}; + +struct SerializedDbGetKeyCountReply { + SerializedSint32 status; + SerializedUint64 keycount; + + SerializedDbGetKeyCountReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + keycount.get_size() + + 0); + } + + void clear() { + status.clear(); + keycount.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + keycount.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + keycount.deserialize(pptr, psize); + } +}; + +struct SerializedDbInsertRequest { + SerializedUint64 db_handle; + SerializedUint64 txn_handle; + SerializedUint32 flags; + SerializedBool has_key; + SerializedKey key; + SerializedBool has_record; + SerializedRecord record; + + SerializedDbInsertRequest() { + clear(); + } + + size_t get_size() const { + return ( + db_handle.get_size() + + txn_handle.get_size() + + flags.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + has_record.get_size() + + (has_record.value ? record.get_size() : 0) + + 0); + } + + void clear() { + db_handle.clear(); + txn_handle.clear(); + flags.clear(); + has_key = false; + key.clear(); + has_record = false; + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + db_handle.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + has_record.serialize(pptr, psize); + if (has_record.value) record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + db_handle.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + has_record.deserialize(pptr, psize); + if (has_record.value) record.deserialize(pptr, psize); + } +}; + +struct SerializedDbInsertReply { + SerializedSint32 status; + SerializedBool has_key; + SerializedKey key; + + SerializedDbInsertReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + 0); + } + + void clear() { + status.clear(); + has_key = false; + key.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + } +}; + +struct SerializedDbEraseRequest { + SerializedUint64 db_handle; + SerializedUint64 txn_handle; + SerializedKey key; + SerializedUint32 flags; + + SerializedDbEraseRequest() { + clear(); + } + + size_t get_size() const { + return ( + db_handle.get_size() + + txn_handle.get_size() + + key.get_size() + + flags.get_size() + + 0); + } + + void clear() { + db_handle.clear(); + txn_handle.clear(); + key.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + db_handle.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + key.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + db_handle.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + key.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedDbEraseReply { + SerializedSint32 status; + + SerializedDbEraseReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedDbFindRequest { + SerializedUint64 db_handle; + SerializedUint64 txn_handle; + SerializedUint64 cursor_handle; + SerializedUint32 flags; + SerializedKey key; + SerializedBool has_record; + SerializedRecord record; + + SerializedDbFindRequest() { + clear(); + } + + size_t get_size() const { + return ( + db_handle.get_size() + + txn_handle.get_size() + + cursor_handle.get_size() + + flags.get_size() + + key.get_size() + + has_record.get_size() + + (has_record.value ? record.get_size() : 0) + + 0); + } + + void clear() { + db_handle.clear(); + txn_handle.clear(); + cursor_handle.clear(); + flags.clear(); + key.clear(); + has_record = false; + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + db_handle.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + cursor_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + key.serialize(pptr, psize); + has_record.serialize(pptr, psize); + if (has_record.value) record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + db_handle.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + cursor_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + key.deserialize(pptr, psize); + has_record.deserialize(pptr, psize); + if (has_record.value) record.deserialize(pptr, psize); + } +}; + +struct SerializedDbFindReply { + SerializedSint32 status; + SerializedBool has_key; + SerializedKey key; + SerializedBool has_record; + SerializedRecord record; + + SerializedDbFindReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + has_record.get_size() + + (has_record.value ? record.get_size() : 0) + + 0); + } + + void clear() { + status.clear(); + has_key = false; + key.clear(); + has_record = false; + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + has_record.serialize(pptr, psize); + if (has_record.value) record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + has_record.deserialize(pptr, psize); + if (has_record.value) record.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCreateRequest { + SerializedUint64 db_handle; + SerializedUint64 txn_handle; + SerializedUint32 flags; + + SerializedCursorCreateRequest() { + clear(); + } + + size_t get_size() const { + return ( + db_handle.get_size() + + txn_handle.get_size() + + flags.get_size() + + 0); + } + + void clear() { + db_handle.clear(); + txn_handle.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + db_handle.serialize(pptr, psize); + txn_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + db_handle.deserialize(pptr, psize); + txn_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCreateReply { + SerializedSint32 status; + SerializedUint64 cursor_handle; + + SerializedCursorCreateReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + cursor_handle.get_size() + + 0); + } + + void clear() { + status.clear(); + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCloneRequest { + SerializedUint64 cursor_handle; + + SerializedCursorCloneRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCloneReply { + SerializedSint32 status; + SerializedUint64 cursor_handle; + + SerializedCursorCloneReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + cursor_handle.get_size() + + 0); + } + + void clear() { + status.clear(); + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCloseRequest { + SerializedUint64 cursor_handle; + + SerializedCursorCloseRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorCloseReply { + SerializedSint32 status; + + SerializedCursorCloseReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedCursorInsertRequest { + SerializedUint64 cursor_handle; + SerializedUint32 flags; + SerializedBool has_key; + SerializedKey key; + SerializedBool has_record; + SerializedRecord record; + + SerializedCursorInsertRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + flags.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + has_record.get_size() + + (has_record.value ? record.get_size() : 0) + + 0); + } + + void clear() { + cursor_handle.clear(); + flags.clear(); + has_key = false; + key.clear(); + has_record = false; + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + has_record.serialize(pptr, psize); + if (has_record.value) record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + has_record.deserialize(pptr, psize); + if (has_record.value) record.deserialize(pptr, psize); + } +}; + +struct SerializedCursorInsertReply { + SerializedSint32 status; + SerializedBool has_key; + SerializedKey key; + + SerializedCursorInsertReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + 0); + } + + void clear() { + status.clear(); + has_key = false; + key.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + } +}; + +struct SerializedCursorEraseRequest { + SerializedUint64 cursor_handle; + SerializedUint32 flags; + + SerializedCursorEraseRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + flags.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedCursorEraseReply { + SerializedSint32 status; + + SerializedCursorEraseReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetRecordCountRequest { + SerializedUint64 cursor_handle; + SerializedUint32 flags; + + SerializedCursorGetRecordCountRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + flags.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetRecordCountReply { + SerializedSint32 status; + SerializedUint32 count; + + SerializedCursorGetRecordCountReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + count.get_size() + + 0); + } + + void clear() { + status.clear(); + count.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + count.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + count.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetRecordSizeRequest { + SerializedUint64 cursor_handle; + + SerializedCursorGetRecordSizeRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetRecordSizeReply { + SerializedSint32 status; + SerializedUint64 size; + + SerializedCursorGetRecordSizeReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + size.get_size() + + 0); + } + + void clear() { + status.clear(); + size.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + size.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + size.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetDuplicatePositionRequest { + SerializedUint64 cursor_handle; + + SerializedCursorGetDuplicatePositionRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + } +}; + +struct SerializedCursorGetDuplicatePositionReply { + SerializedSint32 status; + SerializedUint32 position; + + SerializedCursorGetDuplicatePositionReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + position.get_size() + + 0); + } + + void clear() { + status.clear(); + position.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + position.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + position.deserialize(pptr, psize); + } +}; + +struct SerializedCursorOverwriteRequest { + SerializedUint64 cursor_handle; + SerializedRecord record; + SerializedUint32 flags; + + SerializedCursorOverwriteRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + record.get_size() + + flags.get_size() + + 0); + } + + void clear() { + cursor_handle.clear(); + record.clear(); + flags.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + record.serialize(pptr, psize); + flags.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + record.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + } +}; + +struct SerializedCursorOverwriteReply { + SerializedSint32 status; + + SerializedCursorOverwriteReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + 0); + } + + void clear() { + status.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + } +}; + +struct SerializedCursorMoveRequest { + SerializedUint64 cursor_handle; + SerializedUint32 flags; + SerializedBool has_key; + SerializedKey key; + SerializedBool has_record; + SerializedRecord record; + + SerializedCursorMoveRequest() { + clear(); + } + + size_t get_size() const { + return ( + cursor_handle.get_size() + + flags.get_size() + + has_key.get_size() + + (has_key.value ? key.get_size() : 0) + + has_record.get_size() + + (has_record.value ? record.get_size() : 0) + + 0); + } + + void clear() { + cursor_handle.clear(); + flags.clear(); + has_key = false; + key.clear(); + has_record = false; + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + cursor_handle.serialize(pptr, psize); + flags.serialize(pptr, psize); + has_key.serialize(pptr, psize); + if (has_key.value) key.serialize(pptr, psize); + has_record.serialize(pptr, psize); + if (has_record.value) record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + cursor_handle.deserialize(pptr, psize); + flags.deserialize(pptr, psize); + has_key.deserialize(pptr, psize); + if (has_key.value) key.deserialize(pptr, psize); + has_record.deserialize(pptr, psize); + if (has_record.value) record.deserialize(pptr, psize); + } +}; + +struct SerializedCursorMoveReply { + SerializedSint32 status; + SerializedKey key; + SerializedRecord record; + + SerializedCursorMoveReply() { + clear(); + } + + size_t get_size() const { + return ( + status.get_size() + + key.get_size() + + record.get_size() + + 0); + } + + void clear() { + status.clear(); + key.clear(); + record.clear(); + } + + void serialize(unsigned char **pptr, int *psize) const { + status.serialize(pptr, psize); + key.serialize(pptr, psize); + record.serialize(pptr, psize); + } + + void deserialize(unsigned char **pptr, int *psize) { + status.deserialize(pptr, psize); + key.deserialize(pptr, psize); + record.deserialize(pptr, psize); + } +}; + +struct SerializedWrapper { + SerializedUint32 magic; + SerializedUint32 size; + SerializedUint32 id; + SerializedTxnBeginRequest txn_begin_request; + SerializedTxnBeginReply txn_begin_reply; + SerializedTxnCommitRequest txn_commit_request; + SerializedTxnCommitReply txn_commit_reply; + SerializedTxnAbortRequest txn_abort_request; + SerializedTxnAbortReply txn_abort_reply; + SerializedDbGetKeyCountRequest db_count_request; + SerializedDbGetKeyCountReply db_count_reply; + SerializedDbInsertRequest db_insert_request; + SerializedDbInsertReply db_insert_reply; + SerializedDbEraseRequest db_erase_request; + SerializedDbEraseReply db_erase_reply; + SerializedDbFindRequest db_find_request; + SerializedDbFindReply db_find_reply; + SerializedCursorCreateRequest cursor_create_request; + SerializedCursorCreateReply cursor_create_reply; + SerializedCursorCloneRequest cursor_clone_request; + SerializedCursorCloneReply cursor_clone_reply; + SerializedCursorCloseRequest cursor_close_request; + SerializedCursorCloseReply cursor_close_reply; + SerializedCursorInsertRequest cursor_insert_request; + SerializedCursorInsertReply cursor_insert_reply; + SerializedCursorEraseRequest cursor_erase_request; + SerializedCursorEraseReply cursor_erase_reply; + SerializedCursorGetRecordCountRequest cursor_get_record_count_request; + SerializedCursorGetRecordCountReply cursor_get_record_count_reply; + SerializedCursorGetRecordSizeRequest cursor_get_record_size_request; + SerializedCursorGetRecordSizeReply cursor_get_record_size_reply; + SerializedCursorGetDuplicatePositionRequest cursor_get_duplicate_position_request; + SerializedCursorGetDuplicatePositionReply cursor_get_duplicate_position_reply; + SerializedCursorOverwriteRequest cursor_overwrite_request; + SerializedCursorOverwriteReply cursor_overwrite_reply; + SerializedCursorMoveRequest cursor_move_request; + SerializedCursorMoveReply cursor_move_reply; + + SerializedWrapper() { + clear(); + } + + // the methods in here have a custom implementation, otherwise we would + // generate many bools for the "optional" fields, and they would + // unnecessarily increase the structure size + void clear() { + magic = 0; + size = 0; + id = 0; + } + + size_t get_size() const { + size_t s = magic.get_size() + size.get_size() + id.get_size(); + switch (id.value) { + case kTxnBeginRequest: + return (s + txn_begin_request.get_size()); + case kTxnBeginReply: + return (s + txn_begin_reply.get_size()); + case kTxnCommitRequest: + return (s + txn_commit_request.get_size()); + case kTxnCommitReply: + return (s + txn_commit_reply.get_size()); + case kTxnAbortRequest: + return (s + txn_abort_request.get_size()); + case kTxnAbortReply: + return (s + txn_abort_reply.get_size()); + case kDbGetKeyCountRequest: + return (s + db_count_request.get_size()); + case kDbGetKeyCountReply: + return (s + db_count_reply.get_size()); + case kDbInsertRequest: + return (s + db_insert_request.get_size()); + case kDbInsertReply: + return (s + db_insert_reply.get_size()); + case kDbEraseRequest: + return (s + db_erase_request.get_size()); + case kDbEraseReply: + return (s + db_erase_reply.get_size()); + case kDbFindRequest: + return (s + db_find_request.get_size()); + case kDbFindReply: + return (s + db_find_reply.get_size()); + case kCursorCreateRequest: + return (s + cursor_create_request.get_size()); + case kCursorCreateReply: + return (s + cursor_create_reply.get_size()); + case kCursorCloneRequest: + return (s + cursor_clone_request.get_size()); + case kCursorCloneReply: + return (s + cursor_clone_reply.get_size()); + case kCursorCloseRequest: + return (s + cursor_close_request.get_size()); + case kCursorCloseReply: + return (s + cursor_close_reply.get_size()); + case kCursorInsertRequest: + return (s + cursor_insert_request.get_size()); + case kCursorInsertReply: + return (s + cursor_insert_reply.get_size()); + case kCursorEraseRequest: + return (s + cursor_erase_request.get_size()); + case kCursorEraseReply: + return (s + cursor_erase_reply.get_size()); + case kCursorGetRecordCountRequest: + return (s + cursor_get_record_count_request.get_size()); + case kCursorGetRecordCountReply: + return (s + cursor_get_record_count_reply.get_size()); + case kCursorGetRecordSizeRequest: + return (s + cursor_get_record_size_request.get_size()); + case kCursorGetRecordSizeReply: + return (s + cursor_get_record_size_reply.get_size()); + case kCursorGetDuplicatePositionRequest: + return (s + cursor_get_duplicate_position_request.get_size()); + case kCursorGetDuplicatePositionReply: + return (s + cursor_get_duplicate_position_reply.get_size()); + case kCursorOverwriteRequest: + return (s + cursor_overwrite_request.get_size()); + case kCursorOverwriteReply: + return (s + cursor_overwrite_reply.get_size()); + case kCursorMoveRequest: + return (s + cursor_move_request.get_size()); + case kCursorMoveReply: + return (s + cursor_move_reply.get_size()); + default: + assert(!"shouldn't be here"); + return (0); + } + } + + void serialize(unsigned char **pptr, int *psize) const { + magic.serialize(pptr, psize); + size.serialize(pptr, psize); + id.serialize(pptr, psize); + + switch (id.value) { + case kTxnBeginRequest: + txn_begin_request.serialize(pptr, psize); + break; + case kTxnBeginReply: + txn_begin_reply.serialize(pptr, psize); + break; + case kTxnCommitRequest: + txn_commit_request.serialize(pptr, psize); + break; + case kTxnCommitReply: + txn_commit_reply.serialize(pptr, psize); + break; + case kTxnAbortRequest: + txn_abort_request.serialize(pptr, psize); + break; + case kTxnAbortReply: + txn_abort_reply.serialize(pptr, psize); + break; + case kDbGetKeyCountRequest: + db_count_request.serialize(pptr, psize); + break; + case kDbGetKeyCountReply: + db_count_reply.serialize(pptr, psize); + break; + case kDbInsertRequest: + db_insert_request.serialize(pptr, psize); + break; + case kDbInsertReply: + db_insert_reply.serialize(pptr, psize); + break; + case kDbEraseRequest: + db_erase_request.serialize(pptr, psize); + break; + case kDbEraseReply: + db_erase_reply.serialize(pptr, psize); + break; + case kDbFindRequest: + db_find_request.serialize(pptr, psize); + break; + case kDbFindReply: + db_find_reply.serialize(pptr, psize); + break; + case kCursorCreateRequest: + cursor_create_request.serialize(pptr, psize); + break; + case kCursorCreateReply: + cursor_create_reply.serialize(pptr, psize); + break; + case kCursorCloneRequest: + cursor_clone_request.serialize(pptr, psize); + break; + case kCursorCloneReply: + cursor_clone_reply.serialize(pptr, psize); + break; + case kCursorCloseRequest: + cursor_close_request.serialize(pptr, psize); + break; + case kCursorCloseReply: + cursor_close_reply.serialize(pptr, psize); + break; + case kCursorInsertRequest: + cursor_insert_request.serialize(pptr, psize); + break; + case kCursorInsertReply: + cursor_insert_reply.serialize(pptr, psize); + break; + case kCursorEraseRequest: + cursor_erase_request.serialize(pptr, psize); + break; + case kCursorEraseReply: + cursor_erase_reply.serialize(pptr, psize); + break; + case kCursorGetRecordCountRequest: + cursor_get_record_count_request.serialize(pptr, psize); + break; + case kCursorGetRecordCountReply: + cursor_get_record_count_reply.serialize(pptr, psize); + break; + case kCursorGetRecordSizeRequest: + cursor_get_record_size_request.serialize(pptr, psize); + break; + case kCursorGetRecordSizeReply: + cursor_get_record_size_reply.serialize(pptr, psize); + break; + case kCursorGetDuplicatePositionRequest: + cursor_get_duplicate_position_request.serialize(pptr, psize); + break; + case kCursorGetDuplicatePositionReply: + cursor_get_duplicate_position_reply.serialize(pptr, psize); + break; + case kCursorOverwriteRequest: + cursor_overwrite_request.serialize(pptr, psize); + break; + case kCursorOverwriteReply: + cursor_overwrite_reply.serialize(pptr, psize); + break; + case kCursorMoveRequest: + cursor_move_request.serialize(pptr, psize); + break; + case kCursorMoveReply: + cursor_move_reply.serialize(pptr, psize); + break; + default: + assert(!"shouldn't be here"); + } + } + + void deserialize(unsigned char **pptr, int *psize) { + magic.deserialize(pptr, psize); + size.deserialize(pptr, psize); + id.deserialize(pptr, psize); + + switch (id.value) { + case kTxnBeginRequest: + txn_begin_request.deserialize(pptr, psize); + break; + case kTxnBeginReply: + txn_begin_reply.deserialize(pptr, psize); + break; + case kTxnCommitRequest: + txn_commit_request.deserialize(pptr, psize); + break; + case kTxnCommitReply: + txn_commit_reply.deserialize(pptr, psize); + break; + case kTxnAbortRequest: + txn_abort_request.deserialize(pptr, psize); + break; + case kTxnAbortReply: + txn_abort_reply.deserialize(pptr, psize); + break; + case kDbGetKeyCountRequest: + db_count_request.deserialize(pptr, psize); + break; + case kDbGetKeyCountReply: + db_count_reply.deserialize(pptr, psize); + break; + case kDbInsertRequest: + db_insert_request.deserialize(pptr, psize); + break; + case kDbInsertReply: + db_insert_reply.deserialize(pptr, psize); + break; + case kDbEraseRequest: + db_erase_request.deserialize(pptr, psize); + break; + case kDbEraseReply: + db_erase_reply.deserialize(pptr, psize); + break; + case kDbFindRequest: + db_find_request.deserialize(pptr, psize); + break; + case kDbFindReply: + db_find_reply.deserialize(pptr, psize); + break; + case kCursorCreateRequest: + cursor_create_request.deserialize(pptr, psize); + break; + case kCursorCreateReply: + cursor_create_reply.deserialize(pptr, psize); + break; + case kCursorCloneRequest: + cursor_clone_request.deserialize(pptr, psize); + break; + case kCursorCloneReply: + cursor_clone_reply.deserialize(pptr, psize); + break; + case kCursorCloseRequest: + cursor_close_request.deserialize(pptr, psize); + break; + case kCursorCloseReply: + cursor_close_reply.deserialize(pptr, psize); + break; + case kCursorInsertRequest: + cursor_insert_request.deserialize(pptr, psize); + break; + case kCursorInsertReply: + cursor_insert_reply.deserialize(pptr, psize); + break; + case kCursorEraseRequest: + cursor_erase_request.deserialize(pptr, psize); + break; + case kCursorEraseReply: + cursor_erase_reply.deserialize(pptr, psize); + break; + case kCursorGetRecordCountRequest: + cursor_get_record_count_request.deserialize(pptr, psize); + break; + case kCursorGetRecordCountReply: + cursor_get_record_count_reply.deserialize(pptr, psize); + break; + case kCursorGetRecordSizeRequest: + cursor_get_record_size_request.deserialize(pptr, psize); + break; + case kCursorGetRecordSizeReply: + cursor_get_record_size_reply.deserialize(pptr, psize); + break; + case kCursorGetDuplicatePositionRequest: + cursor_get_duplicate_position_request.deserialize(pptr, psize); + break; + case kCursorGetDuplicatePositionReply: + cursor_get_duplicate_position_reply.deserialize(pptr, psize); + break; + case kCursorOverwriteRequest: + cursor_overwrite_request.deserialize(pptr, psize); + break; + case kCursorOverwriteReply: + cursor_overwrite_reply.serialize(pptr, psize); + break; + case kCursorMoveRequest: + cursor_move_request.deserialize(pptr, psize); + break; + case kCursorMoveReply: + cursor_move_reply.deserialize(pptr, psize); + break; + default: + assert(!"shouldn't be here"); + } + } +}; + + +} // namespace hamsterdb +#endif // HAM_MESSAGES_H + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.proto b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.proto new file mode 100644 index 0000000000..cbd68bf655 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2protoserde/messages.proto @@ -0,0 +1,646 @@ +SET_OPTION(prefix, Serialized) + +PROLOGUE_BEGIN +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_MESSAGES_H +#define HAM_MESSAGES_H + +#include "0root/root.h" + +#include <assert.h> + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +/** a magic and version indicator for the remote protocol */ +#define HAM_TRANSFER_MAGIC_V2 (('h'<<24)|('a'<<16)|('m'<<8)|'2') + +namespace hamsterdb { + +enum { + kTxnBeginRequest, + kTxnBeginReply, + kTxnCommitRequest, + kTxnCommitReply, + kTxnAbortRequest, + kTxnAbortReply, + kDbGetKeyCountRequest, + kDbGetKeyCountReply, + kDbInsertRequest, + kDbInsertReply, + kDbEraseRequest, + kDbEraseReply, + kDbFindRequest, + kDbFindReply, + kCursorCreateRequest, + kCursorCreateReply, + kCursorCloneRequest, + kCursorCloneReply, + kCursorCloseRequest, + kCursorCloseReply, + kCursorInsertRequest, + kCursorInsertReply, + kCursorEraseRequest, + kCursorEraseReply, + kCursorGetRecordCountRequest, + kCursorGetRecordCountReply, + kCursorGetRecordSizeRequest, + kCursorGetRecordSizeReply, + kCursorGetDuplicatePositionRequest, + kCursorGetDuplicatePositionReply, + kCursorOverwriteRequest, + kCursorOverwriteReply, + kCursorMoveRequest, + kCursorMoveReply +}; + +PROLOGUE_END + +MESSAGE_BEGIN(Key) + optional bytes data; + uint32 flags; + uint32 intflags; +MESSAGE_END + +MESSAGE_BEGIN(Record) + optional bytes data; + uint32 flags; + uint32 partial_offset; + uint32 partial_size; +MESSAGE_END + +MESSAGE_BEGIN(ConnectRequest) + bytes path; +MESSAGE_END + +MESSAGE_BEGIN(ConnectReply) + sint32 status; + uint32 env_flags; + uint64 env_handle; +MESSAGE_END + +MESSAGE_BEGIN(TxnBeginRequest) + uint64 env_handle; + uint32 flags; + bytes name; +MESSAGE_END + +MESSAGE_BEGIN(TxnBeginReply) + sint32 status; + uint64 txn_handle; +MESSAGE_END + +MESSAGE_BEGIN(TxnCommitRequest) + uint64 txn_handle; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(TxnCommitReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(TxnAbortRequest) + uint64 txn_handle; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(TxnAbortReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(DbGetKeyCountRequest) + uint64 db_handle; + uint64 txn_handle; + bool distinct; +MESSAGE_END + +MESSAGE_BEGIN(DbGetKeyCountReply) + sint32 status; + uint64 keycount; +MESSAGE_END + +MESSAGE_BEGIN(DbInsertRequest) + uint64 db_handle; + uint64 txn_handle; + uint32 flags; + optional Key key; + optional Record record; +MESSAGE_END + +MESSAGE_BEGIN(DbInsertReply) + sint32 status; + optional Key key; +MESSAGE_END + +MESSAGE_BEGIN(DbEraseRequest) + uint64 db_handle; + uint64 txn_handle; + Key key; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(DbEraseReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(DbFindRequest) + uint64 db_handle; + uint64 txn_handle; + uint64 cursor_handle; + uint32 flags; + Key key; + optional Record record; +MESSAGE_END + +MESSAGE_BEGIN(DbFindReply) + sint32 status; + optional Key key; + optional Record record; +MESSAGE_END + +MESSAGE_BEGIN(CursorCreateRequest) + uint64 db_handle; + uint64 txn_handle; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(CursorCreateReply) + sint32 status; + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorCloneRequest) + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorCloneReply) + sint32 status; + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorCloseRequest) + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorCloseReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(CursorInsertRequest) + uint64 cursor_handle; + uint32 flags; + optional Key key; + optional Record record; +MESSAGE_END + +MESSAGE_BEGIN(CursorInsertReply) + sint32 status; + optional Key key; +MESSAGE_END + +MESSAGE_BEGIN(CursorEraseRequest) + uint64 cursor_handle; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(CursorEraseReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetRecordCountRequest) + uint64 cursor_handle; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetRecordCountReply) + sint32 status; + uint32 count; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetRecordSizeRequest) + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetRecordSizeReply) + sint32 status; + uint64 size; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetDuplicatePositionRequest) + uint64 cursor_handle; +MESSAGE_END + +MESSAGE_BEGIN(CursorGetDuplicatePositionReply) + sint32 status; + uint32 position; +MESSAGE_END + +MESSAGE_BEGIN(CursorOverwriteRequest) + uint64 cursor_handle; + Record record; + uint32 flags; +MESSAGE_END + +MESSAGE_BEGIN(CursorOverwriteReply) + sint32 status; +MESSAGE_END + +MESSAGE_BEGIN(CursorMoveRequest) + uint64 cursor_handle; + uint32 flags; + optional Key key; + optional Record record; +MESSAGE_END + +MESSAGE_BEGIN(CursorMoveReply) + sint32 status; + Key key; + Record record; +MESSAGE_END + +MESSAGE_BEGIN(Wrapper) + uint32 magic; + uint32 size; + uint32 id; + TxnBeginRequest txn_begin_request; + TxnBeginReply txn_begin_reply; + TxnCommitRequest txn_commit_request; + TxnCommitReply txn_commit_reply; + TxnAbortRequest txn_abort_request; + TxnAbortReply txn_abort_reply; + DbGetKeyCountRequest db_count_request; + DbGetKeyCountReply db_count_reply; + DbInsertRequest db_insert_request; + DbInsertReply db_insert_reply; + DbEraseRequest db_erase_request; + DbEraseReply db_erase_reply; + DbFindRequest db_find_request; + DbFindReply db_find_reply; + CursorCreateRequest cursor_create_request; + CursorCreateReply cursor_create_reply; + CursorCloneRequest cursor_clone_request; + CursorCloneReply cursor_clone_reply; + CursorCloseRequest cursor_close_request; + CursorCloseReply cursor_close_reply; + CursorInsertRequest cursor_insert_request; + CursorInsertReply cursor_insert_reply; + CursorEraseRequest cursor_erase_request; + CursorEraseReply cursor_erase_reply; + CursorGetRecordCountRequest cursor_get_record_count_request; + CursorGetRecordCountReply cursor_get_record_count_reply; + CursorGetRecordSizeRequest cursor_get_record_size_request; + CursorGetRecordSizeReply cursor_get_record_size_reply; + CursorGetDuplicatePositionRequest cursor_get_duplicate_position_request; + CursorGetDuplicatePositionReply cursor_get_duplicate_position_reply; + CursorOverwriteRequest cursor_overwrite_request; + CursorOverwriteReply cursor_overwrite_reply; + CursorMoveRequest cursor_move_request; + CursorMoveReply cursor_move_reply; + + CUSTOM_IMPLEMENTATION_BEGIN + // the methods in here have a custom implementation, otherwise we would + // generate many bools for the "optional" fields, and they would + // unnecessarily increase the structure size + void clear() { + magic = 0; + size = 0; + id = 0; + } + + size_t get_size() const { + size_t s = magic.get_size() + size.get_size() + id.get_size(); + switch (id.value) { + case kTxnBeginRequest: + return (s + txn_begin_request.get_size()); + case kTxnBeginReply: + return (s + txn_begin_reply.get_size()); + case kTxnCommitRequest: + return (s + txn_commit_request.get_size()); + case kTxnCommitReply: + return (s + txn_commit_reply.get_size()); + case kTxnAbortRequest: + return (s + txn_abort_request.get_size()); + case kTxnAbortReply: + return (s + txn_abort_reply.get_size()); + case kDbGetKeyCountRequest: + return (s + db_count_request.get_size()); + case kDbGetKeyCountReply: + return (s + db_count_reply.get_size()); + case kDbInsertRequest: + return (s + db_insert_request.get_size()); + case kDbInsertReply: + return (s + db_insert_reply.get_size()); + case kDbEraseRequest: + return (s + db_erase_request.get_size()); + case kDbEraseReply: + return (s + db_erase_reply.get_size()); + case kDbFindRequest: + return (s + db_find_request.get_size()); + case kDbFindReply: + return (s + db_find_reply.get_size()); + case kCursorCreateRequest: + return (s + cursor_create_request.get_size()); + case kCursorCreateReply: + return (s + cursor_create_reply.get_size()); + case kCursorCloneRequest: + return (s + cursor_clone_request.get_size()); + case kCursorCloneReply: + return (s + cursor_clone_reply.get_size()); + case kCursorCloseRequest: + return (s + cursor_close_request.get_size()); + case kCursorCloseReply: + return (s + cursor_close_reply.get_size()); + case kCursorInsertRequest: + return (s + cursor_insert_request.get_size()); + case kCursorInsertReply: + return (s + cursor_insert_reply.get_size()); + case kCursorEraseRequest: + return (s + cursor_erase_request.get_size()); + case kCursorEraseReply: + return (s + cursor_erase_reply.get_size()); + case kCursorGetRecordCountRequest: + return (s + cursor_get_record_count_request.get_size()); + case kCursorGetRecordCountReply: + return (s + cursor_get_record_count_reply.get_size()); + case kCursorGetRecordSizeRequest: + return (s + cursor_get_record_size_request.get_size()); + case kCursorGetRecordSizeReply: + return (s + cursor_get_record_size_reply.get_size()); + case kCursorGetDuplicatePositionRequest: + return (s + cursor_get_duplicate_position_request.get_size()); + case kCursorGetDuplicatePositionReply: + return (s + cursor_get_duplicate_position_reply.get_size()); + case kCursorOverwriteRequest: + return (s + cursor_overwrite_request.get_size()); + case kCursorOverwriteReply: + return (s + cursor_overwrite_reply.get_size()); + case kCursorMoveRequest: + return (s + cursor_move_request.get_size()); + case kCursorMoveReply: + return (s + cursor_move_reply.get_size()); + default: + assert(!"shouldn't be here"); + return (0); + } + } + + void serialize(unsigned char **pptr, int *psize) const { + magic.serialize(pptr, psize); + size.serialize(pptr, psize); + id.serialize(pptr, psize); + + switch (id.value) { + case kTxnBeginRequest: + txn_begin_request.serialize(pptr, psize); + break; + case kTxnBeginReply: + txn_begin_reply.serialize(pptr, psize); + break; + case kTxnCommitRequest: + txn_commit_request.serialize(pptr, psize); + break; + case kTxnCommitReply: + txn_commit_reply.serialize(pptr, psize); + break; + case kTxnAbortRequest: + txn_abort_request.serialize(pptr, psize); + break; + case kTxnAbortReply: + txn_abort_reply.serialize(pptr, psize); + break; + case kDbGetKeyCountRequest: + db_count_request.serialize(pptr, psize); + break; + case kDbGetKeyCountReply: + db_count_reply.serialize(pptr, psize); + break; + case kDbInsertRequest: + db_insert_request.serialize(pptr, psize); + break; + case kDbInsertReply: + db_insert_reply.serialize(pptr, psize); + break; + case kDbEraseRequest: + db_erase_request.serialize(pptr, psize); + break; + case kDbEraseReply: + db_erase_reply.serialize(pptr, psize); + break; + case kDbFindRequest: + db_find_request.serialize(pptr, psize); + break; + case kDbFindReply: + db_find_reply.serialize(pptr, psize); + break; + case kCursorCreateRequest: + cursor_create_request.serialize(pptr, psize); + break; + case kCursorCreateReply: + cursor_create_reply.serialize(pptr, psize); + break; + case kCursorCloneRequest: + cursor_clone_request.serialize(pptr, psize); + break; + case kCursorCloneReply: + cursor_clone_reply.serialize(pptr, psize); + break; + case kCursorCloseRequest: + cursor_close_request.serialize(pptr, psize); + break; + case kCursorCloseReply: + cursor_close_reply.serialize(pptr, psize); + break; + case kCursorInsertRequest: + cursor_insert_request.serialize(pptr, psize); + break; + case kCursorInsertReply: + cursor_insert_reply.serialize(pptr, psize); + break; + case kCursorEraseRequest: + cursor_erase_request.serialize(pptr, psize); + break; + case kCursorEraseReply: + cursor_erase_reply.serialize(pptr, psize); + break; + case kCursorGetRecordCountRequest: + cursor_get_record_count_request.serialize(pptr, psize); + break; + case kCursorGetRecordCountReply: + cursor_get_record_count_reply.serialize(pptr, psize); + break; + case kCursorGetRecordSizeRequest: + cursor_get_record_size_request.serialize(pptr, psize); + break; + case kCursorGetRecordSizeReply: + cursor_get_record_size_reply.serialize(pptr, psize); + break; + case kCursorGetDuplicatePositionRequest: + cursor_get_duplicate_position_request.serialize(pptr, psize); + break; + case kCursorGetDuplicatePositionReply: + cursor_get_duplicate_position_reply.serialize(pptr, psize); + break; + case kCursorOverwriteRequest: + cursor_overwrite_request.serialize(pptr, psize); + break; + case kCursorOverwriteReply: + cursor_overwrite_reply.serialize(pptr, psize); + break; + case kCursorMoveRequest: + cursor_move_request.serialize(pptr, psize); + break; + case kCursorMoveReply: + cursor_move_reply.serialize(pptr, psize); + break; + default: + assert(!"shouldn't be here"); + } + } + + void deserialize(unsigned char **pptr, int *psize) { + magic.deserialize(pptr, psize); + size.deserialize(pptr, psize); + id.deserialize(pptr, psize); + + switch (id.value) { + case kTxnBeginRequest: + txn_begin_request.deserialize(pptr, psize); + break; + case kTxnBeginReply: + txn_begin_reply.deserialize(pptr, psize); + break; + case kTxnCommitRequest: + txn_commit_request.deserialize(pptr, psize); + break; + case kTxnCommitReply: + txn_commit_reply.deserialize(pptr, psize); + break; + case kTxnAbortRequest: + txn_abort_request.deserialize(pptr, psize); + break; + case kTxnAbortReply: + txn_abort_reply.deserialize(pptr, psize); + break; + case kDbGetKeyCountRequest: + db_count_request.deserialize(pptr, psize); + break; + case kDbGetKeyCountReply: + db_count_reply.deserialize(pptr, psize); + break; + case kDbInsertRequest: + db_insert_request.deserialize(pptr, psize); + break; + case kDbInsertReply: + db_insert_reply.deserialize(pptr, psize); + break; + case kDbEraseRequest: + db_erase_request.deserialize(pptr, psize); + break; + case kDbEraseReply: + db_erase_reply.deserialize(pptr, psize); + break; + case kDbFindRequest: + db_find_request.deserialize(pptr, psize); + break; + case kDbFindReply: + db_find_reply.deserialize(pptr, psize); + break; + case kCursorCreateRequest: + cursor_create_request.deserialize(pptr, psize); + break; + case kCursorCreateReply: + cursor_create_reply.deserialize(pptr, psize); + break; + case kCursorCloneRequest: + cursor_clone_request.deserialize(pptr, psize); + break; + case kCursorCloneReply: + cursor_clone_reply.deserialize(pptr, psize); + break; + case kCursorCloseRequest: + cursor_close_request.deserialize(pptr, psize); + break; + case kCursorCloseReply: + cursor_close_reply.deserialize(pptr, psize); + break; + case kCursorInsertRequest: + cursor_insert_request.deserialize(pptr, psize); + break; + case kCursorInsertReply: + cursor_insert_reply.deserialize(pptr, psize); + break; + case kCursorEraseRequest: + cursor_erase_request.deserialize(pptr, psize); + break; + case kCursorEraseReply: + cursor_erase_reply.deserialize(pptr, psize); + break; + case kCursorGetRecordCountRequest: + cursor_get_record_count_request.deserialize(pptr, psize); + break; + case kCursorGetRecordCountReply: + cursor_get_record_count_reply.deserialize(pptr, psize); + break; + case kCursorGetRecordSizeRequest: + cursor_get_record_size_request.deserialize(pptr, psize); + break; + case kCursorGetRecordSizeReply: + cursor_get_record_size_reply.deserialize(pptr, psize); + break; + case kCursorGetDuplicatePositionRequest: + cursor_get_duplicate_position_request.deserialize(pptr, psize); + break; + case kCursorGetDuplicatePositionReply: + cursor_get_duplicate_position_reply.deserialize(pptr, psize); + break; + case kCursorOverwriteRequest: + cursor_overwrite_request.deserialize(pptr, psize); + break; + case kCursorOverwriteReply: + cursor_overwrite_reply.serialize(pptr, psize); + break; + case kCursorMoveRequest: + cursor_move_request.deserialize(pptr, psize); + break; + case kCursorMoveReply: + cursor_move_reply.deserialize(pptr, psize); + break; + default: + assert(!"shouldn't be here"); + } + } + CUSTOM_IMPLEMENTATION_END +MESSAGE_END + + +EPILOGUE_BEGIN + +} // namespace hamsterdb +#endif // HAM_MESSAGES_H + +EPILOGUE_END diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2queue/queue.h b/plugins/Dbx_kv/src/hamsterdb/src/2queue/queue.h new file mode 100644 index 0000000000..a45d45dfa2 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2queue/queue.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A thread-safe message queue. Producers can insert at the front, Consumers + * pick messages from the tail. + * + * The queue uses a Spinlock for synchronization, but locks it only very, + * very briefly. + */ + +#ifndef HAM_QUEUE_H +#define HAM_QUEUE_H + +#include "0root/root.h" + +#include <ham/types.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/spinlock.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// The Message. Other messages can derive from it and append their own +// payload. +struct MessageBase +{ + // Message flags + enum { + // Message is mandatory and must not be skipped + kIsMandatory = 0 + }; + + MessageBase(int type_, int flags_) + : type(type_), flags(flags_), previous(0), next(0) { + } + + virtual ~MessageBase() { + } + + int type; + int flags; + MessageBase *previous; + MessageBase *next; +}; + + +class Queue +{ + public: + template<typename T> + struct Message : public MessageBase + { + Message(int type, int flags) + : MessageBase(type, flags) { + } + + T payload; + }; + + Queue() + : m_head(0), m_tail(0) { + } + + // Pushes a |message| object to the queue + void push(MessageBase *message) { + ScopedSpinlock lock(m_mutex); + if (!m_tail) { + ham_assert(m_head == 0); + m_head = m_tail = message; + } + else if (m_tail == m_head) { + m_tail->previous = message; + message->next = m_tail; + m_head = message; + } + else { + message->next = m_head; + m_head->previous = message; + m_head = message; + } + } + + // Pops a message from the tail of the queue. Returns null if the queue + // is empty. + MessageBase *pop() { + ScopedSpinlock lock(m_mutex); + if (!m_tail) { + ham_assert(m_head == 0); + return (0); + } + + MessageBase *msg = m_tail; + if (m_tail == m_head) + m_head = m_tail = 0; + else + m_tail = m_tail->previous; + return (msg); + } + + private: + // For synchronization + Spinlock m_mutex; + + // The head of the linked list (and newest MessageBase) + MessageBase *m_head; + + // The tail of the linked list (and oldest MessageBase) + MessageBase *m_tail; +}; + +} // namespace hamsterdb + +#endif // HAM_QUEUE_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/2worker/worker.h b/plugins/Dbx_kv/src/hamsterdb/src/2worker/worker.h new file mode 100644 index 0000000000..2f6798b32c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/2worker/worker.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The worker thread. Asynchronously purges the cache. Thread will start as + * soon as it's constructed. + */ + +#ifndef HAM_WORKER_H +#define HAM_WORKER_H + +#include "0root/root.h" + +#include <boost/thread.hpp> + +// Always verify that a file of level N does not include headers > N! +#include "2queue/queue.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Worker +{ + public: + Worker() + : m_stop_requested(false), m_thread(&Worker::run, this) { + } + + void add_to_queue(MessageBase *message) { + m_queue.push(message); + + ScopedLock lock(m_mutex); + m_cond.notify_one(); + } + + void stop_and_join() { + { + ScopedLock lock(m_mutex); + m_stop_requested = true; + m_cond.notify_one(); + } + m_thread.join(); + } + + private: + // The thread function + void run() { + while (true) { + MessageBase *message = 0; + { + ScopedLock lock(m_mutex); + if (m_stop_requested) + return; + message = m_queue.pop(); + if (!message) { + m_cond.wait(lock); // will unlock m_mutex while waiting + message = m_queue.pop(); + } + } + + if (message) { + handle_message(message); + delete message; + } + } + } + + // The message handler - has to be overridden + virtual void handle_message(MessageBase *message) = 0; + + // A queue for storing messages + Queue m_queue; + + // true if the Environment is closed + bool m_stop_requested; + + // A mutex for protecting |m_cond| + boost::mutex m_mutex; + + // A condition to wait for + boost::condition_variable m_cond; + + // The actual thread + boost::thread m_thread; +}; + +} // namespace hamsterdb + +#endif // HAM_WORKER_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.cc b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.cc new file mode 100644 index 0000000000..d0c075cdec --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.cc @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "blob_manager.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +uint64_t +BlobManager::allocate(Context *context, ham_record_t *record, + uint32_t flags) +{ + // PARTIAL WRITE + // + // if offset+partial_size equals the full record size, then we won't + // have any gaps. In this case we just write the full record and ignore + // the partial parameters. + if (flags & HAM_PARTIAL) { + if (record->partial_offset == 0 && record->partial_size == record->size) + flags &= ~HAM_PARTIAL; + } + + m_metric_total_allocated++; + + return (do_allocate(context, record, flags)); +} + +void +BlobManager::read(Context *context, uint64_t blobid, ham_record_t *record, + uint32_t flags, ByteArray *arena) +{ + m_metric_total_read++; + + return (do_read(context, blobid, record, flags, arena)); +} + +uint64_t +BlobManager::overwrite(Context *context, uint64_t old_blobid, + ham_record_t *record, uint32_t flags) +{ + // PARTIAL WRITE + // + // if offset+partial_size equals the full record size, then we won't + // have any gaps. In this case we just write the full record and ignore + // the partial parameters. + if (flags & HAM_PARTIAL) { + if (record->partial_offset == 0 && record->partial_size == record->size) + flags &= ~HAM_PARTIAL; + } + + return (do_overwrite(context, old_blobid, record, flags)); +} + +uint64_t +BlobManager::get_blob_size(Context *context, uint64_t blob_id) +{ + return (do_get_blob_size(context, blob_id)); +} + +void +BlobManager::erase(Context *context, uint64_t blob_id, Page *page, + uint32_t flags) +{ + return (do_erase(context, blob_id, page, flags)); +} + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.h b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.h new file mode 100644 index 0000000000..208345e2ed --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @brief functions for reading/writing/allocating blobs (memory chunks of + * arbitrary size) + * + */ + +#ifndef HAM_BLOB_MANAGER_H +#define HAM_BLOB_MANAGER_H + +#include "0root/root.h" + +#include "ham/hamsterdb_int.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "2page/page.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class LocalEnvironment; + +#include "1base/packstart.h" + +// A blob header structure +// +// This header is prepended to the blob's payload. It holds the blob size and +// the blob's address (which is not required but useful for error checking.) +HAM_PACK_0 class HAM_PACK_1 PBlobHeader +{ + public: + PBlobHeader() { + memset(this, 0, sizeof(PBlobHeader)); + } + + // Returns a PBlobHeader from a file address + static PBlobHeader *from_page(Page *page, uint64_t address) { + uint32_t readstart = (uint32_t)(address - page->get_address()); + return (PBlobHeader *)&page->get_raw_payload()[readstart]; + } + + // Returns the blob flags + uint32_t get_flags() const { + return (m_flags); + } + + // Sets the blob's flags + void set_flags(uint32_t flags) { + m_flags = flags; + } + + // Returns the absolute address of the blob + uint64_t get_self() const { + return (m_blobid); + } + + // Sets the absolute address of the blob + void set_self(uint64_t id) { + m_blobid = id; + } + + // Returns the payload size of the blob + uint64_t get_size() const { + return (m_size); + } + + // Sets the payload size of the blob + void set_size(uint64_t size) { + m_size = size; + } + + // Returns the allocated size of the blob (includes padding) + uint64_t get_alloc_size() const { + return (m_allocated_size); + } + + // Sets the allocated size of a blob (includes padding) + void set_alloc_size(uint64_t size) { + m_allocated_size = size; + } + + private: + // Flags; currently only used in hamsterdb-pro to store compression + // information + uint32_t m_flags; + + // The blob ID - which is the absolute address/offset of this + //* structure in the file + uint64_t m_blobid; + + // The allocated size of the blob; this is the size, which is used + // by the blob and it's header and maybe additional padding + uint64_t m_allocated_size; + + // The "real" size of the blob (excluding the header) + uint64_t m_size; +} HAM_PACK_2; + +#include "1base/packstop.h" + +// The BlobManager manages blobs (not a surprise) +// +// This is an abstract baseclass, derived for In-Memory- and Disk-based +// Environments. +class BlobManager +{ + protected: + // Flags for the PBlobHeader structure + enum { + // Blob is compressed + kIsCompressed = 1 + }; + + public: + // Flags for allocate(); make sure that they do not conflict with + // the flags for ham_db_insert() + enum { + // Do not compress the blob, even if compression is enabled + kDisableCompression = 0x10000000 + }; + + BlobManager(LocalEnvironment *env) + : m_env(env), m_metric_before_compression(0), + m_metric_after_compression(0), m_metric_total_allocated(0), + m_metric_total_read(0) { + } + + virtual ~BlobManager() { } + + // Allocates/create a new blob. + // This function returns the blob-id (the start address of the blob + // header) + // + // |flags| can be HAM_PARTIAL, kDisableCompression + uint64_t allocate(Context *context, ham_record_t *record, uint32_t flags); + + // Reads a blob and stores the data in @a record. + // @ref flags: either 0 or HAM_DIRECT_ACCESS + void read(Context *context, uint64_t blob_id, ham_record_t *record, + uint32_t flags, ByteArray *arena); + + // Retrieves the size of a blob + uint64_t get_blob_size(Context *context, uint64_t blob_id); + + // Overwrites an existing blob + // + // Will return an error if the blob does not exist. Returns the blob-id + // (the start address of the blob header) + uint64_t overwrite(Context *context, uint64_t old_blob_id, + ham_record_t *record, uint32_t flags); + + // Deletes an existing blob + void erase(Context *context, uint64_t blob_id, Page *page = 0, + uint32_t flags = 0); + + // Fills in the current metrics + void fill_metrics(ham_env_metrics_t *metrics) const { + metrics->blob_total_allocated = m_metric_total_allocated; + metrics->blob_total_read = m_metric_total_read; + metrics->record_bytes_before_compression = m_metric_before_compression; + metrics->record_bytes_after_compression = m_metric_after_compression; + } + + protected: + // Allocates/create a new blob. + // This function returns the blob-id (the start address of the blob + // header) + virtual uint64_t do_allocate(Context *context, ham_record_t *record, + uint32_t flags) = 0; + + // Reads a blob and stores the data in @a record. + // @ref flags: either 0 or HAM_DIRECT_ACCESS + virtual void do_read(Context *context, uint64_t blob_id, + ham_record_t *record, uint32_t flags, + ByteArray *arena) = 0; + + // Retrieves the size of a blob + virtual uint64_t do_get_blob_size(Context *context, + uint64_t blob_id) = 0; + + // Overwrites an existing blob + // + // Will return an error if the blob does not exist. Returns the blob-id + // (the start address of the blob header) + virtual uint64_t do_overwrite(Context *context, uint64_t old_blob_id, + ham_record_t *record, uint32_t flags) = 0; + + // Deletes an existing blob + virtual void do_erase(Context *context, uint64_t blob_id, + Page *page = 0, uint32_t flags = 0) = 0; + + // The Environment which created this BlobManager + LocalEnvironment *m_env; + + // Usage tracking - number of bytes before compression + uint64_t m_metric_before_compression; + + // Usage tracking - number of bytes after compression + uint64_t m_metric_after_compression; + + private: + // Usage tracking - number of blobs allocated + uint64_t m_metric_total_allocated; + + // Usage tracking - number of blobs read + uint64_t m_metric_total_read; +}; + +} // namespace hamsterdb + +#endif /* HAM_BLOB_MANAGER_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.cc b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.cc new file mode 100644 index 0000000000..231789774b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.cc @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <algorithm> +#include <vector> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "2device/device.h" +#include "3blob_manager/blob_manager_disk.h" +#include "3page_manager/page_manager.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +uint64_t +DiskBlobManager::do_allocate(Context *context, ham_record_t *record, + uint32_t flags) +{ + uint8_t *chunk_data[2]; + uint32_t chunk_size[2]; + uint32_t page_size = m_env->config().page_size_bytes; + + PBlobHeader blob_header; + uint32_t alloc_size = sizeof(PBlobHeader) + record->size; + + // first check if we can add another blob to the last used page + Page *page = m_env->page_manager()->get_last_blob_page(context); + + PBlobPageHeader *header = 0; + uint64_t address = 0; + if (page) { + header = PBlobPageHeader::from_page(page); + // allocate space for the blob + if (!alloc_from_freelist(header, alloc_size, &address)) + page = 0; + else + address += page->get_address(); + } + + if (!address) { + // Allocate a new page. If the blob exceeds a page then allocate multiple + // pages that are directly next to each other. + uint32_t required_size = alloc_size + kPageOverhead; + uint32_t num_pages = required_size / page_size; + if (num_pages * page_size < required_size) + num_pages++; + + // |page| now points to the first page that was allocated, and + // the only one which has a header and a freelist + page = m_env->page_manager()->alloc_multiple_blob_pages(context, num_pages); + ham_assert(page->is_without_header() == false); + + // initialize the PBlobPageHeader + header = PBlobPageHeader::from_page(page); + header->initialize(); + header->set_num_pages(num_pages); + header->set_free_bytes((num_pages * page_size) - kPageOverhead); + + // and move the remaining space to the freelist, unless we span multiple + // pages (then the rest will be discarded) - TODO can we reuse it somehow? + if (num_pages == 1 + && kPageOverhead + alloc_size > 0 + && header->get_free_bytes() - alloc_size > 0) { + header->set_freelist_offset(0, kPageOverhead + alloc_size); + header->set_freelist_size(0, header->get_free_bytes() - alloc_size); + } + + address = page->get_address() + kPageOverhead; + ham_assert(check_integrity(header)); + } + + // addjust "free bytes" counter + ham_assert(header->get_free_bytes() >= alloc_size); + header->set_free_bytes(header->get_free_bytes() - alloc_size); + + // store the page id if it still has space left + if (header->get_free_bytes()) + m_env->page_manager()->set_last_blob_page(page); + else + m_env->page_manager()->set_last_blob_page(0); + + // initialize the blob header + blob_header.set_alloc_size(alloc_size); + blob_header.set_size(record->size); + blob_header.set_self(address); + + // PARTIAL WRITE + // + // Are there gaps at the beginning? If yes, then we'll fill with zeros + ByteArray zeroes; + if ((flags & HAM_PARTIAL) && (record->partial_offset > 0)) { + uint32_t gapsize = record->partial_offset; + + // first: write the header + chunk_data[0] = (uint8_t *)&blob_header; + chunk_size[0] = sizeof(blob_header); + write_chunks(context, page, address, chunk_data, chunk_size, 1); + + address += sizeof(blob_header); + + // now fill the gap; if the gap is bigger than a pagesize we'll + // split the gap into smaller chunks + while (gapsize) { + uint32_t size = gapsize >= page_size + ? page_size + : gapsize; + chunk_data[0] = (uint8_t *)zeroes.resize(size, 0); + chunk_size[0] = size; + write_chunks(context, page, address, chunk_data, chunk_size, 1); + gapsize -= size; + address += size; + } + + // now write the "real" data + chunk_data[0] = (uint8_t *)record->data; + chunk_size[0] = record->partial_size; + + write_chunks(context, page, address, chunk_data, chunk_size, 1); + address += record->partial_size; + } + else { + // not writing partially: write header and data, then we're done + chunk_data[0] = (uint8_t *)&blob_header; + chunk_size[0] = sizeof(blob_header); + chunk_data[1] = (uint8_t *)record->data; + chunk_size[1] = (flags & HAM_PARTIAL) + ? record->partial_size + : record->size; + + write_chunks(context, page, address, chunk_data, chunk_size, 2); + address += chunk_size[0] + chunk_size[1]; + } + + // store the blobid; it will be returned to the caller + uint64_t blobid = blob_header.get_self(); + + // PARTIAL WRITES: + // + // if we have gaps at the end of the blob: just append more chunks to + // fill these gaps. Since they can be pretty large we split them into + // smaller chunks if necessary. + if (flags & HAM_PARTIAL) { + if (record->partial_offset + record->partial_size < record->size) { + uint32_t gapsize = record->size + - (record->partial_offset + record->partial_size); + + // now fill the gap; if the gap is bigger than a pagesize we'll + // split the gap into smaller chunks + // + // we split this loop in two - the outer loop will allocate the + // memory buffer, thus saving some allocations + while (gapsize) { + uint32_t size = gapsize > page_size + ? page_size + : gapsize; + chunk_data[0] = (uint8_t *)zeroes.resize(size, 0); + chunk_size[0] = size; + write_chunks(context, page, address, chunk_data, chunk_size, 1); + gapsize -= size; + address += size; + } + } + } + + ham_assert(check_integrity(header)); + + return (blobid); +} + +void +DiskBlobManager::do_read(Context *context, uint64_t blobid, + ham_record_t *record, uint32_t flags, ByteArray *arena) +{ + Page *page; + + // first step: read the blob header + PBlobHeader *blob_header = (PBlobHeader *)read_chunk(context, 0, &page, + blobid, true); + + // sanity check + if (blob_header->get_self() != blobid) { + ham_log(("blob %lld not found", blobid)); + throw Exception(HAM_BLOB_NOT_FOUND); + } + + uint32_t blobsize = (uint32_t)blob_header->get_size(); + record->size = blobsize; + + if (flags & HAM_PARTIAL) { + if (record->partial_offset > blobsize) { + ham_trace(("partial offset is greater than the total record size")); + throw Exception(HAM_INV_PARAMETER); + } + if (record->partial_offset + record->partial_size > blobsize) + record->partial_size = blobsize = blobsize - record->partial_offset; + else + blobsize = record->partial_size; + } + + // empty blob? + if (!blobsize) { + record->data = 0; + record->size = 0; + return; + } + + // if the blob is in memory-mapped storage (and the user does not require + // a copy of the data): simply return a pointer + if ((flags & HAM_FORCE_DEEP_COPY) == 0 + && m_env->device()->is_mapped(blobid, blobsize) + && !(record->flags & HAM_RECORD_USER_ALLOC)) { + record->data = read_chunk(context, page, 0, + blobid + sizeof(PBlobHeader) + (flags & HAM_PARTIAL + ? record->partial_offset + : 0), true); + } + // otherwise resize the blob buffer and copy the blob data into the buffer + else { + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + arena->resize(blobsize); + record->data = arena->get_ptr(); + } + + copy_chunk(context, page, 0, + blobid + sizeof(PBlobHeader) + (flags & HAM_PARTIAL + ? record->partial_offset + : 0), + (uint8_t *)record->data, blobsize, true); + } +} + +uint64_t +DiskBlobManager::do_get_blob_size(Context *context, uint64_t blobid) +{ + // read the blob header + PBlobHeader *blob_header = (PBlobHeader *)read_chunk(context, 0, 0, blobid, + true); + + if (blob_header->get_self() != blobid) + throw Exception(HAM_BLOB_NOT_FOUND); + + return (blob_header->get_size()); +} + +uint64_t +DiskBlobManager::do_overwrite(Context *context, uint64_t old_blobid, + ham_record_t *record, uint32_t flags) +{ + PBlobHeader *old_blob_header, new_blob_header; + Page *page; + + uint32_t alloc_size = sizeof(PBlobHeader) + record->size; + + // first, read the blob header; if the new blob fits into the + // old blob, we overwrite the old blob (and add the remaining + // space to the freelist, if there is any) + old_blob_header = (PBlobHeader *)read_chunk(context, 0, &page, + old_blobid, false); + + // sanity check + ham_assert(old_blob_header->get_self() == old_blobid); + if (old_blob_header->get_self() != old_blobid) + throw Exception(HAM_BLOB_NOT_FOUND); + + // now compare the sizes; does the new data fit in the old allocated + // space? + if (alloc_size <= old_blob_header->get_alloc_size()) { + uint8_t *chunk_data[2]; + uint32_t chunk_size[2]; + + // setup the new blob header + new_blob_header.set_self(old_blob_header->get_self()); + new_blob_header.set_size(record->size); + new_blob_header.set_alloc_size(alloc_size); + new_blob_header.set_flags(0); // disable compression, just in case... + + // PARTIAL WRITE + // + // if we have a gap at the beginning, then we have to write the + // blob header and the blob data in two steps; otherwise we can + // write both immediately + if ((flags & HAM_PARTIAL) && (record->partial_offset)) { + chunk_data[0] = (uint8_t *)&new_blob_header; + chunk_size[0] = sizeof(new_blob_header); + write_chunks(context, page, new_blob_header.get_self(), + chunk_data, chunk_size, 1); + + chunk_data[0] = (uint8_t *)record->data; + chunk_size[0] = record->partial_size; + write_chunks(context, page, new_blob_header.get_self() + + sizeof(new_blob_header) + record->partial_offset, + chunk_data, chunk_size, 1); + } + else { + chunk_data[0] = (uint8_t *)&new_blob_header; + chunk_size[0] = sizeof(new_blob_header); + chunk_data[1] = (uint8_t *)record->data; + chunk_size[1] = (flags & HAM_PARTIAL) + ? record->partial_size + : record->size; + + write_chunks(context, page, new_blob_header.get_self(), + chunk_data, chunk_size, 2); + } + + // move remaining data to the freelist + if (alloc_size < old_blob_header->get_alloc_size()) { + PBlobPageHeader *header = PBlobPageHeader::from_page(page); + header->set_free_bytes(header->get_free_bytes() + + (uint32_t)(old_blob_header->get_alloc_size() - alloc_size)); + add_to_freelist(header, + (uint32_t)(old_blobid + alloc_size) - page->get_address(), + (uint32_t)old_blob_header->get_alloc_size() - alloc_size); + } + + // the old rid is the new rid + return (new_blob_header.get_self()); + } + + // if the new data is larger: allocate a fresh space for it + // and discard the old; 'overwrite' has become (delete + insert) now. + uint64_t new_blobid = allocate(context, record, flags); + erase(context, old_blobid, 0, 0); + + return (new_blobid); +} + +void +DiskBlobManager::do_erase(Context *context, uint64_t blobid, Page *page, + uint32_t flags) +{ + // fetch the blob header + PBlobHeader *blob_header = (PBlobHeader *)read_chunk(context, 0, &page, + blobid, false); + + // sanity check + ham_verify(blob_header->get_self() == blobid); + if (blob_header->get_self() != blobid) + throw Exception(HAM_BLOB_NOT_FOUND); + + // update the "free bytes" counter in the blob page header + PBlobPageHeader *header = PBlobPageHeader::from_page(page); + header->set_free_bytes(header->get_free_bytes() + + blob_header->get_alloc_size()); + + // if the page is now completely empty (all blobs were erased) then move + // it to the freelist + if (header->get_free_bytes() == (header->get_num_pages() + * m_env->config().page_size_bytes) - kPageOverhead) { + m_env->page_manager()->set_last_blob_page(0); + m_env->page_manager()->del(context, page, header->get_num_pages()); + header->initialize(); + return; + } + + // otherwise move the blob to the freelist + add_to_freelist(header, (uint32_t)(blobid - page->get_address()), + (uint32_t)blob_header->get_alloc_size()); +} + +bool +DiskBlobManager::alloc_from_freelist(PBlobPageHeader *header, uint32_t size, + uint64_t *poffset) +{ + ham_assert(check_integrity(header)); + + // freelist is not used if this is a multi-page blob + if (header->get_num_pages() > 1) + return (false); + + uint32_t count = header->get_freelist_entries(); + + for (uint32_t i = 0; i < count; i++) { + // exact match + if (header->get_freelist_size(i) == size) { + *poffset = header->get_freelist_offset(i); + header->set_freelist_offset(i, 0); + header->set_freelist_size(i, 0); + ham_assert(check_integrity(header)); + return (true); + } + // space in freelist is larger than what we need? return this space, + // make sure the remaining gap stays in the freelist + if (header->get_freelist_size(i) > size) { + *poffset = header->get_freelist_offset(i); + header->set_freelist_offset(i, (uint32_t)(*poffset + size)); + header->set_freelist_size(i, header->get_freelist_size(i) - size); + ham_assert(check_integrity(header)); + return (true); + } + } + + // there was no gap large enough for the blob + return (false); +} + +void +DiskBlobManager::add_to_freelist(PBlobPageHeader *header, + uint32_t offset, uint32_t size) +{ + ham_assert(check_integrity(header)); + + // freelist is not used if this is a multi-page blob + if (header->get_num_pages() > 1) + return; + + uint32_t count = header->get_freelist_entries(); + + // first try to collapse the blobs + for (uint32_t i = 0; i < count; i++) { + if (offset + size == header->get_freelist_offset(i)) { + header->set_freelist_offset(i, offset); + header->set_freelist_size(i, header->get_freelist_size(i) + size); + ham_assert(check_integrity(header)); + return; + } + if (header->get_freelist_offset(i) + header->get_freelist_size(i) + == offset) { + header->set_freelist_size(i, header->get_freelist_size(i) + size); + ham_assert(check_integrity(header)); + return; + } + } + + // otherwise store the blob in a new slot, if available + uint32_t smallest = 0; + for (uint32_t i = 0; i < count; i++) { + // slot is empty + if (header->get_freelist_size(i) == 0) { + header->set_freelist_offset(i, offset); + header->set_freelist_size(i, size); + ham_assert(check_integrity(header)); + return; + } + // otherwise look for the smallest entry + if (header->get_freelist_size(i) < header->get_freelist_size(smallest)) { + smallest = i; + continue; + } + } + + // overwrite the smallest entry? + if (size > header->get_freelist_size(smallest)) { + header->set_freelist_offset(smallest, offset); + header->set_freelist_size(smallest, size); + } + + ham_assert(check_integrity(header)); +} + +bool +DiskBlobManager::check_integrity(PBlobPageHeader *header) const +{ + ham_assert(header->get_num_pages() > 0); + + if (header->get_free_bytes() + kPageOverhead + > (m_env->config().page_size_bytes * header->get_num_pages())) { + ham_trace(("integrity violated: free bytes exceeds page boundary")); + return (false); + } + + // freelist is not used if this is a multi-page blob + if (header->get_num_pages() > 1) + return (true); + + uint32_t count = header->get_freelist_entries(); + uint32_t total_sizes = 0; + typedef std::pair<uint32_t, uint32_t> Range; + typedef std::vector<Range> RangeVec; + RangeVec ranges; + + for (uint32_t i = 0; i < count - 1; i++) { + if (header->get_freelist_size(i) == 0) { + ham_assert(header->get_freelist_offset(i) == 0); + continue; + } + total_sizes += header->get_freelist_size(i); + ranges.push_back(std::make_pair(header->get_freelist_offset(i), + header->get_freelist_size(i))); + } + + // the sum of freelist chunks must not exceed total number of free bytes + if (total_sizes > header->get_free_bytes()) { + ham_trace(("integrity violated: total freelist slots exceed free bytes")); + return (false); + } + + std::sort(ranges.begin(), ranges.end()); + + if (!ranges.empty()) { + for (uint32_t i = 0; i < ranges.size() - 1; i++) { + if (ranges[i].first + ranges[i].second + > m_env->config().page_size_bytes * header->get_num_pages()) { + ham_trace(("integrity violated: freelist slot %u/%u exceeds page", + ranges[i].first, ranges[i].second)); + return (false); + } + if (ranges[i].first + ranges[i].second > ranges[i + 1].first) { + ham_trace(("integrity violated: freelist slot %u/%u overlaps with %lu", + ranges[i].first, ranges[i].second, + ranges[i + 1].first)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + } + + return (true); +} + +void +DiskBlobManager::write_chunks(Context *context, Page *page, + uint64_t address, uint8_t **chunk_data, uint32_t *chunk_size, + uint32_t chunks) +{ + uint32_t page_size = m_env->config().page_size_bytes; + + // for each chunk... + for (uint32_t i = 0; i < chunks; i++) { + uint32_t size = chunk_size[i]; + uint8_t *data = chunk_data[i]; + + while (size) { + // get the page-id from this chunk + uint64_t pageid = address - (address % page_size); + + // is this the current page? if yes then continue working with this page, + // otherwise fetch the page + if (page && page->get_address() != pageid) + page = 0; + if (!page) + page = m_env->page_manager()->fetch(context, pageid, + PageManager::kNoHeader); + + uint32_t write_start = (uint32_t)(address - page->get_address()); + uint32_t write_size = (uint32_t)(page_size - write_start); + + // now write the data + if (write_size > size) + write_size = size; + memcpy(&page->get_raw_payload()[write_start], data, write_size); + page->set_dirty(true); + address += write_size; + data += write_size; + size -= write_size; + } + } +} + +void +DiskBlobManager::copy_chunk(Context *context, Page *page, Page **ppage, + uint64_t address, uint8_t *data, uint32_t size, + bool fetch_read_only) +{ + uint32_t page_size = m_env->config().page_size_bytes; + bool first_page = true; + + while (size) { + // get the page-id from this chunk + uint64_t pageid = address - (address % page_size); + + // is this the current page? if yes then continue working with this page, + // otherwise fetch the page + if (page && page->get_address() != pageid) + page = 0; + + if (!page) { + uint32_t flags = 0; + if (fetch_read_only) + flags |= PageManager::kReadOnly; + if (!first_page) + flags |= PageManager::kNoHeader; + page = m_env->page_manager()->fetch(context, pageid, flags); + } + + // now read the data from the page + uint32_t read_start = (uint32_t)(address - page->get_address()); + uint32_t read_size = (uint32_t)(page_size - read_start); + if (read_size > size) + read_size = size; + memcpy(data, &page->get_raw_payload()[read_start], read_size); + address += read_size; + data += read_size; + size -= read_size; + + first_page = false; + } + + if (ppage) + *ppage = page; +} + +uint8_t * +DiskBlobManager::read_chunk(Context *context, Page *page, Page **ppage, + uint64_t address, bool fetch_read_only) +{ + // get the page-id from this chunk + uint32_t page_size = m_env->config().page_size_bytes; + uint64_t pageid = address - (address % page_size); + + // is this the current page? if yes then continue working with this page, + // otherwise fetch the page + if (page && page->get_address() != pageid) + page = 0; + + if (!page) { + uint32_t flags = 0; + if (fetch_read_only) + flags |= PageManager::kReadOnly; + page = m_env->page_manager()->fetch(context, pageid, flags); + if (ppage) + *ppage = page; + } + + uint32_t read_start = (uint32_t)(address - page->get_address()); + return (&page->get_raw_payload()[read_start]); +} diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.h b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.h new file mode 100644 index 0000000000..7ec8b67d95 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_disk.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAM_BLOB_MANAGER_DISK_H +#define HAM_BLOB_MANAGER_DISK_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3blob_manager/blob_manager.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +#include "1base/packstart.h" + +/* + * The header of a blob page + * + * Contains a fixed length freelist and a couter for the number of free + * bytes + */ +HAM_PACK_0 class HAM_PACK_1 PBlobPageHeader +{ + public: + void initialize() { + memset(this, 0, sizeof(PBlobPageHeader)); + } + + // Returns a PBlobPageHeader from a page + static PBlobPageHeader *from_page(Page *page) { + return (PBlobPageHeader *)&page->get_payload()[0]; + } + + // Returns the number of pages which are all managed by this header + uint32_t get_num_pages() const { + return (m_num_pages); + } + + // Sets the number of pages which are all managed by this header + void set_num_pages(uint32_t num_pages) { + m_num_pages = num_pages; + } + + // Returns the "free bytes" counter + uint32_t get_free_bytes() const { + return (m_free_bytes); + } + + // Sets the "free bytes" counter + void set_free_bytes(uint32_t free_bytes) { + m_free_bytes = free_bytes; + } + + // Returns the total number of freelist entries + uint8_t get_freelist_entries() const { + return (32); + } + + // Returns the offset of freelist entry |i| + uint32_t get_freelist_offset(uint32_t i) const { + return (m_freelist[i].offset); + } + + // Sets the offset of freelist entry |i| + void set_freelist_offset(uint32_t i, uint32_t offset) { + m_freelist[i].offset = offset; + } + + // Returns the size of freelist entry |i| + uint32_t get_freelist_size(uint32_t i) const { + return (m_freelist[i].size); + } + + // Sets the size of freelist entry |i| + void set_freelist_size(uint32_t i, uint32_t size) { + m_freelist[i].size = size; + } + + private: + // Number of "regular" pages for this blob; used for blobs exceeding + // a page size + uint32_t m_num_pages; + + // Number of free bytes in this page + uint32_t m_free_bytes; + + struct FreelistEntry { + uint32_t offset; + uint32_t size; + }; + + // The freelist - offset/size pairs in this page + FreelistEntry m_freelist[32]; +} HAM_PACK_2; + +#include "1base/packstop.h" + + +/* + * A BlobManager for disk-based databases + */ +class DiskBlobManager : public BlobManager +{ + enum { + // Overhead per page + kPageOverhead = Page::kSizeofPersistentHeader + sizeof(PBlobPageHeader) + }; + + public: + DiskBlobManager(LocalEnvironment *env) + : BlobManager(env) { + } + + protected: + // allocate/create a blob + // returns the blob-id (the start address of the blob header) + virtual uint64_t do_allocate(Context *context, ham_record_t *record, + uint32_t flags); + + // reads a blob and stores the data in |record|. The pointer |record.data| + // is backed by the |arena|, unless |HAM_RECORD_USER_ALLOC| is set. + // flags: either 0 or HAM_DIRECT_ACCESS + virtual void do_read(Context *context, uint64_t blobid, + ham_record_t *record, uint32_t flags, + ByteArray *arena); + + // retrieves the size of a blob + virtual uint64_t do_get_blob_size(Context *context, uint64_t blobid); + + // overwrite an existing blob + // + // will return an error if the blob does not exist + // returns the blob-id (the start address of the blob header) in |blobid| + virtual uint64_t do_overwrite(Context *context, uint64_t old_blobid, + ham_record_t *record, uint32_t flags); + + // delete an existing blob + virtual void do_erase(Context *context, uint64_t blobid, + Page *page = 0, uint32_t flags = 0); + + private: + friend class DuplicateManager; + friend struct BlobManagerFixture; + + // write a series of data chunks to storage at file offset 'addr'. + // + // The chunks are assumed to be stored in sequential order, adjacent + // to each other, i.e. as one long data strip. + void write_chunks(Context *context, Page *page, uint64_t addr, + uint8_t **chunk_data, uint32_t *chunk_size, + uint32_t chunks); + + // Same as above, but for reading chunks from the file. The data + // is copied to |data|. + void copy_chunk(Context *context, Page *page, Page **fpage, + uint64_t addr, uint8_t *data, uint32_t size, + bool fetch_read_only); + + // Same as |copy_chunk|, but does not copy the data + uint8_t *read_chunk(Context *context, Page *page, Page **fpage, + uint64_t addr, bool fetch_read_only); + + // adds a free chunk to the freelist + void add_to_freelist(PBlobPageHeader *header, uint32_t offset, + uint32_t size); + + // searches the freelist for a free chunk; if available, returns |true| + // and stores the offset in |poffset|. + bool alloc_from_freelist(PBlobPageHeader *header, uint32_t size, + uint64_t *poffset); + + // verifies the integrity of the freelist + bool check_integrity(PBlobPageHeader *header) const; +}; + +} // namespace hamsterdb + +#endif /* HAM_BLOB_MANAGER_DISK_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_factory.h b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_factory.h new file mode 100644 index 0000000000..129849c7ad --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_factory.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAM_BLOB_MANAGER_FACTORY_H +#define HAM_BLOB_MANAGER_FACTORY_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3blob_manager/blob_manager_disk.h" +#include "3blob_manager/blob_manager_inmem.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct BlobManagerFactory { + // creates a new BlobManager instance depending on the flags + static BlobManager *create(LocalEnvironment *env, uint32_t flags) { + if (flags & HAM_IN_MEMORY) + return (new InMemoryBlobManager(env)); + else + return (new DiskBlobManager(env)); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_BLOB_MANAGER_FACTORY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.cc b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.cc new file mode 100644 index 0000000000..1044d815c5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.cc @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "2device/device_inmem.h" +#include "3blob_manager/blob_manager_inmem.h" +#include "4db/db_local.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +uint64_t +InMemoryBlobManager::do_allocate(Context *context, ham_record_t *record, + uint32_t flags) +{ + // in-memory-database: the blobid is actually a pointer to the memory + // buffer, in which the blob (with the blob-header) is stored + uint8_t *p = (uint8_t *)m_env->device()->alloc(record->size + + sizeof(PBlobHeader)); + + // initialize the header + PBlobHeader *blob_header = (PBlobHeader *)p; + memset(blob_header, 0, sizeof(*blob_header)); + blob_header->set_self((uint64_t)PTR_TO_U64(p)); + blob_header->set_alloc_size(record->size + sizeof(PBlobHeader)); + blob_header->set_size(record->size); + + // do we have gaps? if yes, fill them with zeroes + if (flags & HAM_PARTIAL) { + uint8_t *s = p + sizeof(PBlobHeader); + if (record->partial_offset) + memset(s, 0, record->partial_offset); + memcpy(s + record->partial_offset, record->data, record->partial_size); + if (record->partial_offset + record->partial_size < record->size) + memset(s + record->partial_offset + record->partial_size, 0, + record->size - (record->partial_offset + record->partial_size)); + } + else { + memcpy(p + sizeof(PBlobHeader), record->data, record->size); + } + + return ((uint64_t)PTR_TO_U64(p)); +} + +void +InMemoryBlobManager::do_read(Context *context, uint64_t blobid, + ham_record_t *record, uint32_t flags, + ByteArray *arena) +{ + // in-memory-database: the blobid is actually a pointer to the memory + // buffer, in which the blob is stored + PBlobHeader *blob_header = (PBlobHeader *)U64_TO_PTR(blobid); + uint8_t *data = (uint8_t *)(U64_TO_PTR(blobid)) + sizeof(PBlobHeader); + + // when the database is closing, the header is already deleted + if (!blob_header) { + record->size = 0; + return; + } + + uint32_t blobsize = (uint32_t)blob_header->get_size(); + record->size = blobsize; + + if (flags & HAM_PARTIAL) { + if (record->partial_offset > blobsize) { + ham_trace(("partial offset is greater than the total record size")); + throw Exception(HAM_INV_PARAMETER); + } + if (record->partial_offset + record->partial_size > blobsize) + record->partial_size = blobsize = blobsize - record->partial_offset; + else + blobsize = record->partial_size; + } + + // empty blob? + if (!blobsize) { + record->data = 0; + record->size = 0; + } + else { + uint8_t *d = data; + if (flags & HAM_PARTIAL) + d += record->partial_offset; + + if ((flags & HAM_DIRECT_ACCESS) + && !(record->flags & HAM_RECORD_USER_ALLOC)) { + record->data = d; + } + else { + // resize buffer if necessary + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + arena->resize(blobsize); + record->data = arena->get_ptr(); + } + // and copy the data + memcpy(record->data, d, blobsize); + } + } +} + +uint64_t +InMemoryBlobManager::do_overwrite(Context *context, uint64_t old_blobid, + ham_record_t *record, uint32_t flags) +{ + // free the old blob, allocate a new blob (but if both sizes are equal, + // just overwrite the data) + PBlobHeader *phdr = (PBlobHeader *)U64_TO_PTR(old_blobid); + + if (phdr->get_size() == record->size) { + uint8_t *p = (uint8_t *)phdr; + if (flags & HAM_PARTIAL) { + memmove(p + sizeof(PBlobHeader) + record->partial_offset, + record->data, record->partial_size); + } + else { + memmove(p + sizeof(PBlobHeader), record->data, record->size); + } + return ((uint64_t)PTR_TO_U64(phdr)); + } + else { + uint64_t new_blobid = allocate(context, record, flags); + + InMemoryDevice *dev = (InMemoryDevice *)m_env->device(); + dev->release(phdr, (size_t)phdr->get_alloc_size()); + return (new_blobid); + } +} + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.h b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.h new file mode 100644 index 0000000000..3c5b19a9fa --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3blob_manager/blob_manager_inmem.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAM_BLOB_MANAGER_INMEM_H +#define HAM_BLOB_MANAGER_INMEM_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3blob_manager/blob_manager.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/** + * A BlobManager for in-memory blobs + */ +class InMemoryBlobManager : public BlobManager { + public: + InMemoryBlobManager(LocalEnvironment *env) + : BlobManager(env) { + } + + protected: + // Allocates/create a new blob + // This function returns the blob-id (the start address of the blob + // header) + virtual uint64_t do_allocate(Context *context, ham_record_t *record, + uint32_t flags); + + // Reads a blob and stores the data in |record| + // |flags|: either 0 or HAM_DIRECT_ACCESS + virtual void do_read(Context *context, uint64_t blobid, + ham_record_t *record, uint32_t flags, + ByteArray *arena); + + // Retrieves the size of a blob + virtual uint64_t do_get_blob_size(Context *context, uint64_t blobid) { + PBlobHeader *blob_header = (PBlobHeader *)U64_TO_PTR(blobid); + return ((uint32_t)blob_header->get_size()); + } + + // Overwrites an existing blob + // + // Will return an error if the blob does not exist. Returns the blob-id + // (the start address of the blob header) + virtual uint64_t do_overwrite(Context *context, uint64_t old_blobid, + ham_record_t *record, uint32_t flags); + + // Deletes an existing blob + virtual void do_erase(Context *context, uint64_t blobid, + Page *page = 0, uint32_t flags = 0) { + Memory::release((void *)U64_TO_PTR(blobid)); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_BLOB_MANAGER_INMEM_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_check.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_check.cc new file mode 100644 index 0000000000..73098ce3e1 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_check.cc @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree verification + */ + +#include "0root/root.h" + +#include <set> +#include <string.h> +#include <stdio.h> +#if HAM_DEBUG +# include <sstream> +# include <fstream> +#endif + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "2page/page.h" +#include "3page_manager/page_manager.h" +#include "3page_manager/page_manager_test.h" +#include "3btree/btree_index.h" +#include "3btree/btree_node_proxy.h" +#include "4db/db.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class BtreeCheckAction +{ + public: + // Constructor + BtreeCheckAction(BtreeIndex *btree, Context *context, uint32_t flags) + : m_btree(btree), m_context(context), m_flags(flags) { + } + + // This is the main method; it starts the verification. + void run() { + Page *page, *parent = 0; + uint32_t level = 0; + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + ham_assert(m_btree->get_root_address() != 0); + + // get the root page of the tree + page = env->page_manager()->fetch(m_context, m_btree->get_root_address(), + PageManager::kReadOnly); + +#if HAM_DEBUG + if (m_flags & HAM_PRINT_GRAPH) { + m_graph << "digraph g {" << std::endl + << " graph [" << std::endl + << " rankdir = \"TD\"" << std::endl + << " ];" << std::endl + << " node [" << std::endl + << " fontsize = \"8\"" << std::endl + << " shape = \"ellipse\"" << std::endl + << " ];" << std::endl + << " edge [" << std::endl + << " ];" << std::endl; + } +#endif + + // for each level... + while (page) { + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + uint64_t ptr_down = node->get_ptr_down(); + + // verify the page and all its siblings + verify_level(parent, page, level); + parent = page; + + // follow the pointer to the smallest child + if (ptr_down) + page = env->page_manager()->fetch(m_context, ptr_down, + PageManager::kReadOnly); + else + page = 0; + + ++level; + } + +#if HAM_DEBUG + if (m_flags & HAM_PRINT_GRAPH) { + m_graph << "}" << std::endl; + + std::ofstream file; + file.open("graph.dot"); + file << m_graph.str(); + } +#endif + } + + private: + // Verifies a whole level in the tree - start with "page" and traverse + // the linked list of all the siblings + void verify_level(Page *parent, Page *page, uint32_t level) { + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + Page *child, *leftsib = 0; + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + // assert that the parent page's smallest item (item 0) is bigger + // than the largest item in this page + if (parent && node->get_left()) { + int cmp = compare_keys(db, page, 0, node->get_count() - 1); + if (cmp <= 0) { + ham_log(("integrity check failed in page 0x%llx: parent item " + "#0 <= item #%d\n", page->get_address(), + node->get_count() - 1)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + + m_children.clear(); + + while (page) { + // verify the page + verify_page(parent, leftsib, page, level); + + // follow the right sibling + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + if (node->get_right()) + child = env->page_manager()->fetch(m_context, + node->get_right(), PageManager::kReadOnly); + else + child = 0; + + if (leftsib) { + BtreeNodeProxy *leftnode = m_btree->get_node_from_page(leftsib); + ham_assert(leftnode->is_leaf() == node->is_leaf()); + } + + leftsib = page; + page = child; + } + } + + // Verifies a single page + void verify_page(Page *parent, Page *leftsib, Page *page, uint32_t level) { + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + +#if HAM_DEBUG + if (m_flags & HAM_PRINT_GRAPH) { + std::stringstream ss; + ss << "node" << page->get_address(); + m_graph << " \"" << ss.str() << "\" [" << std::endl + << " label = \""; + m_graph << "<fl>L|<fd>D|"; + for (uint32_t i = 0; i < node->get_count(); i++) { + m_graph << "<f" << i << ">" << i << "|"; + } + m_graph << "<fr>R\"" << std::endl + << " shape = \"record\"" << std::endl + << " ];" << std::endl; +#if 0 + // edge to the left sibling + if (node->get_left()) + m_graph << "\"" << ss.str() << "\":fl -> \"node" + << node->get_left() << "\":fr [" << std::endl + << " ];" << std::endl; + // to the right sibling + if (node->get_right()) + m_graph << " \"" << ss.str() << "\":fr -> \"node" + << node->get_right() << "\":fl [" << std::endl + << " ];" << std::endl; +#endif + // to ptr_down + if (node->get_ptr_down()) + m_graph << " \"" << ss.str() << "\":fd -> \"node" + << node->get_ptr_down() << "\":fd [" << std::endl + << " ];" << std::endl; + // to all children + if (!node->is_leaf()) { + for (uint32_t i = 0; i < node->get_count(); i++) { + m_graph << " \"" << ss.str() << "\":f" << i << " -> \"node" + << node->get_record_id(m_context, i) << "\":fd [" + << std::endl << " ];" << std::endl; + } + } + } +#endif + + if (node->get_count() == 0) { + // a rootpage can be empty! check if this page is the rootpage + if (page->get_address() == m_btree->get_root_address()) + return; + + // for internal nodes: ptr_down HAS to be set! + if (!node->is_leaf() && node->get_ptr_down() == 0) { + ham_log(("integrity check failed in page 0x%llx: empty page!\n", + page->get_address())); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + + // check if the largest item of the left sibling is smaller than + // the smallest item of this page + if (leftsib) { + BtreeNodeProxy *sibnode = m_btree->get_node_from_page(leftsib); + ham_key_t key1 = {0}; + ham_key_t key2 = {0}; + + node->check_integrity(m_context); + + if (node->get_count() > 0 && sibnode->get_count() > 0) { + sibnode->get_key(m_context, sibnode->get_count() - 1, + &m_barray1, &key1); + node->get_key(m_context, 0, &m_barray2, &key2); + + int cmp = node->compare(&key1, &key2); + if (cmp >= 0) { + ham_log(("integrity check failed in page 0x%llx: item #0 " + "< left sibling item #%d\n", page->get_address(), + sibnode->get_count() - 1)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + } + + if (node->get_count() == 1) + return; + + node->check_integrity(m_context); + + if (node->get_count() > 0) { + for (uint32_t i = 0; i < node->get_count() - 1; i++) { + int cmp = compare_keys(db, page, (uint32_t)i, (uint32_t)(i + 1)); + if (cmp >= 0) { + ham_log(("integrity check failed in page 0x%llx: item #%d " + "< item #%d", page->get_address(), i, i + 1)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + } + + // internal nodes: make sure that all record IDs are unique + if (!node->is_leaf()) { + if (m_children.find(node->get_ptr_down()) != m_children.end()) { + ham_log(("integrity check failed in page 0x%llx: record of item " + "-1 is not unique", page->get_address())); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + m_children.insert(node->get_ptr_down()); + + for (uint32_t i = 0; i < node->get_count(); i++) { + uint64_t child_id = node->get_record_id(m_context, i); + if (m_children.find(child_id) != m_children.end()) { + ham_log(("integrity check failed in page 0x%llx: record of item " + "#%d is not unique", page->get_address(), i)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + PageManagerTest test = env->page_manager()->test(); + if (test.is_page_free(child_id)) { + ham_log(("integrity check failed in page 0x%llx: record of item " + "#%d is in freelist", page->get_address(), i)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + m_children.insert(child_id); + } + } + } + + int compare_keys(LocalDatabase *db, Page *page, int lhs, int rhs) { + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + ham_key_t key1 = {0}; + ham_key_t key2 = {0}; + + node->get_key(m_context, lhs, &m_barray1, &key1); + node->get_key(m_context, rhs, &m_barray2, &key2); + + return (node->compare(&key1, &key2)); + } + + // The BtreeIndex on which we operate + BtreeIndex *m_btree; + + // The current Context + Context *m_context; + + // The flags as specified when calling ham_db_check_integrity + uint32_t m_flags; + + // ByteArrays to avoid frequent memory allocations + ByteArray m_barray1; + ByteArray m_barray2; + + // For checking uniqueness of record IDs on an internal level + std::set<uint64_t> m_children; + +#if HAM_DEBUG + // For printing the graph + std::ostringstream m_graph; +#endif +}; + +void +BtreeIndex::check_integrity(Context *context, uint32_t flags) +{ + BtreeCheckAction bta(this, context, flags); + bta.run(); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.cc new file mode 100644 index 0000000000..b66b58c645 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.cc @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "2page/page.h" +#include "3page_manager/page_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_cursor.h" +#include "3btree/btree_node_proxy.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +BtreeCursor::BtreeCursor(Cursor *parent) + : m_parent(parent), m_state(0), m_duplicate_index(0), + m_coupled_page(0), m_coupled_index(0), m_next_in_page(0), + m_previous_in_page(0) +{ + memset(&m_uncoupled_key, 0, sizeof(m_uncoupled_key)); + m_btree = parent->get_db()->btree_index(); +} + +void +BtreeCursor::set_to_nil() +{ + // uncoupled cursor: free the cached pointer + if (m_state == kStateUncoupled) + memset(&m_uncoupled_key, 0, sizeof(m_uncoupled_key)); + // coupled cursor: remove from page + else if (m_state == kStateCoupled) + remove_cursor_from_page(m_coupled_page); + + m_state = BtreeCursor::kStateNil; + m_duplicate_index = 0; +} + +void +BtreeCursor::uncouple_from_page(Context *context) +{ + if (m_state == kStateUncoupled || m_state == kStateNil) + return; + + ham_assert(m_coupled_page != 0); + + // get the btree-entry of this key + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + ham_assert(node->is_leaf()); + node->get_key(context, m_coupled_index, &m_uncoupled_arena, &m_uncoupled_key); + + // uncouple the page + remove_cursor_from_page(m_coupled_page); + + // set the state and the uncoupled key + m_state = BtreeCursor::kStateUncoupled; +} + +void +BtreeCursor::clone(BtreeCursor *other) +{ + m_duplicate_index = other->m_duplicate_index; + + // if the old cursor is coupled: couple the new cursor, too + if (other->m_state == kStateCoupled) { + couple_to_page(other->m_coupled_page, other->m_coupled_index); + } + // otherwise, if the src cursor is uncoupled: copy the key + else if (other->m_state == kStateUncoupled) { + memset(&m_uncoupled_key, 0, sizeof(m_uncoupled_key)); + + m_uncoupled_arena.copy(other->m_uncoupled_arena.get_ptr(), + other->m_uncoupled_arena.get_size()); + m_uncoupled_key.data = m_uncoupled_arena.get_ptr(); + m_uncoupled_key.size = m_uncoupled_arena.get_size(); + m_state = kStateUncoupled; + } + else { + set_to_nil(); + } +} + +void +BtreeCursor::overwrite(Context *context, ham_record_t *record, uint32_t flags) +{ + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + throw Exception(HAM_CURSOR_IS_NIL); + + // copy the key flags, and remove all flags concerning the key size + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + node->set_record(context, m_coupled_index, record, m_duplicate_index, + flags | HAM_OVERWRITE, 0); + + m_coupled_page->set_dirty(true); +} + +ham_status_t +BtreeCursor::move(Context *context, ham_key_t *key, ByteArray *key_arena, + ham_record_t *record, ByteArray *record_arena, uint32_t flags) +{ + ham_status_t st = 0; + + if (flags & HAM_CURSOR_FIRST) + st = move_first(context, flags); + else if (flags & HAM_CURSOR_LAST) + st = move_last(context, flags); + else if (flags & HAM_CURSOR_NEXT) + st = move_next(context, flags); + else if (flags & HAM_CURSOR_PREVIOUS) + st = move_previous(context, flags); + // no move, but cursor is nil? return error + else if (m_state == kStateNil) { + if (key || record) + return (HAM_CURSOR_IS_NIL); + else + return (0); + } + // no move, but cursor is not coupled? couple it + else if (m_state == kStateUncoupled) + couple(context); + + if (st) + return (st); + + ham_assert(m_state == kStateCoupled); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + ham_assert(node->is_leaf()); + + if (key) + node->get_key(context, m_coupled_index, key_arena, key); + + if (record) + node->get_record(context, m_coupled_index, record_arena, record, + flags, m_duplicate_index); + + return (0); +} + +ham_status_t +BtreeCursor::find(Context *context, ham_key_t *key, ByteArray *key_arena, + ham_record_t *record, ByteArray *record_arena, uint32_t flags) +{ + set_to_nil(); + + return (m_btree->find(context, m_parent, key, key_arena, record, + record_arena, flags)); +} + +bool +BtreeCursor::points_to(Context *context, Page *page, int slot) +{ + if (m_state == kStateUncoupled) + couple(context); + + if (m_state == kStateCoupled) + return (m_coupled_page == page && m_coupled_index == slot); + + return (false); +} + +bool +BtreeCursor::points_to(Context *context, ham_key_t *key) +{ + if (m_state == kStateUncoupled) { + if (m_uncoupled_key.size != key->size) + return (false); + return (0 == m_btree->compare_keys(key, &m_uncoupled_key)); + } + + if (m_state == kStateCoupled) { + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + return (node->equals(context, key, m_coupled_index)); + } + + ham_assert(!"shouldn't be here"); + return (false); +} + +ham_status_t +BtreeCursor::move_to_next_page(Context *context) +{ + LocalEnvironment *env = m_parent->get_db()->lenv(); + + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + return (HAM_CURSOR_IS_NIL); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + // if there is no right sibling then couple the cursor to the right-most + // key in the last page and return KEY_NOT_FOUND + if (!node->get_right()) { + couple_to_page(m_coupled_page, node->get_count() - 1, 0); + return (HAM_KEY_NOT_FOUND); + } + + Page *page = env->page_manager()->fetch(context, node->get_right(), + PageManager::kReadOnly); + couple_to_page(page, 0, 0); + return (0); +} + +int +BtreeCursor::get_record_count(Context *context, uint32_t flags) +{ + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + throw Exception(HAM_CURSOR_IS_NIL); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + return (node->get_record_count(context, m_coupled_index)); +} + +uint64_t +BtreeCursor::get_record_size(Context *context) +{ + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + throw Exception(HAM_CURSOR_IS_NIL); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + return (node->get_record_size(context, m_coupled_index, m_duplicate_index)); +} + +void +BtreeCursor::couple(Context *context) +{ + ham_assert(m_state == kStateUncoupled); + + /* + * Make a 'find' on the cached key; if we succeed, the cursor + * is automatically coupled. Since |find()| overwrites and modifies + * the cursor's state, keep a backup and restore it afterwards. + */ + int duplicate_index = m_duplicate_index; + ByteArray uncoupled_arena = m_uncoupled_arena; + ham_key_t uncoupled_key = m_uncoupled_key; + m_uncoupled_arena = ByteArray(); + + find(context, &uncoupled_key, 0, 0, 0, 0); + + m_duplicate_index = duplicate_index; + m_uncoupled_key = uncoupled_key; + m_uncoupled_arena = uncoupled_arena; + uncoupled_arena.disown(); // do not free when going out of scope +} + +ham_status_t +BtreeCursor::move_first(Context *context, uint32_t flags) +{ + LocalDatabase *db = m_parent->get_db(); + LocalEnvironment *env = db->lenv(); + + // get a NIL cursor + set_to_nil(); + + // get the root page + Page *page = env->page_manager()->fetch(context, + m_btree->get_root_address(), PageManager::kReadOnly); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + // traverse down to the leafs + while (!node->is_leaf()) { + page = env->page_manager()->fetch(context, node->get_ptr_down(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // and to the next page that is NOT empty + while (node->get_count() == 0) { + if (node->get_right() == 0) + return (HAM_KEY_NOT_FOUND); + page = env->page_manager()->fetch(context, node->get_right(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // couple this cursor to the smallest key in this page + couple_to_page(page, 0, 0); + + return (0); +} + +ham_status_t +BtreeCursor::move_next(Context *context, uint32_t flags) +{ + LocalDatabase *db = m_parent->get_db(); + LocalEnvironment *env = db->lenv(); + + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + return (HAM_CURSOR_IS_NIL); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + + // if this key has duplicates: get the next duplicate; otherwise + // (and if there's no duplicate): fall through + if (!(flags & HAM_SKIP_DUPLICATES)) { + if (m_duplicate_index + < node->get_record_count(context, m_coupled_index) - 1) { + m_duplicate_index++; + return (0); + } + } + + // don't continue if ONLY_DUPLICATES is set + if (flags & HAM_ONLY_DUPLICATES) + return (HAM_KEY_NOT_FOUND); + + // if the index+1 is still in the coupled page, just increment the index + if (m_coupled_index + 1 < (int)node->get_count()) { + couple_to_page(m_coupled_page, m_coupled_index + 1, 0); + return (0); + } + + // otherwise uncouple the cursor and load the right sibling page + if (!node->get_right()) + return (HAM_KEY_NOT_FOUND); + + Page *page = env->page_manager()->fetch(context, node->get_right(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + + // if the right node is empty then continue searching for the next + // non-empty page + while (node->get_count() == 0) { + if (!node->get_right()) + return (HAM_KEY_NOT_FOUND); + page = env->page_manager()->fetch(context, node->get_right(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // couple this cursor to the smallest key in this page + couple_to_page(page, 0, 0); + + return (0); +} + +ham_status_t +BtreeCursor::move_previous(Context *context, uint32_t flags) +{ + LocalDatabase *db = m_parent->get_db(); + LocalEnvironment *env = db->lenv(); + + // uncoupled cursor: couple it + if (m_state == kStateUncoupled) + couple(context); + else if (m_state != kStateCoupled) + return (HAM_CURSOR_IS_NIL); + + BtreeNodeProxy *node = m_btree->get_node_from_page(m_coupled_page); + + // if this key has duplicates: get the previous duplicate; otherwise + // (and if there's no duplicate): fall through + if (!(flags & HAM_SKIP_DUPLICATES) && m_duplicate_index > 0) { + m_duplicate_index--; + return (0); + } + + // don't continue if ONLY_DUPLICATES is set + if (flags & HAM_ONLY_DUPLICATES) + return (HAM_KEY_NOT_FOUND); + + // if the index-1 is till in the coupled page, just decrement the index + if (m_coupled_index != 0) { + couple_to_page(m_coupled_page, m_coupled_index - 1); + } + // otherwise load the left sibling page + else { + if (!node->get_left()) + return (HAM_KEY_NOT_FOUND); + + Page *page = env->page_manager()->fetch(context, node->get_left(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + + // if the left node is empty then continue searching for the next + // non-empty page + while (node->get_count() == 0) { + if (!node->get_left()) + return (HAM_KEY_NOT_FOUND); + page = env->page_manager()->fetch(context, node->get_left(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // couple this cursor to the highest key in this page + couple_to_page(page, node->get_count() - 1); + } + m_duplicate_index = 0; + + // if duplicates are enabled: move to the end of the duplicate-list + if (!(flags & HAM_SKIP_DUPLICATES)) + m_duplicate_index = node->get_record_count(context, m_coupled_index) - 1; + + return (0); +} + +ham_status_t +BtreeCursor::move_last(Context *context, uint32_t flags) +{ + LocalDatabase *db = m_parent->get_db(); + LocalEnvironment *env = db->lenv(); + + // get a NIL cursor + set_to_nil(); + + // get the root page + if (!m_btree->get_root_address()) + return (HAM_KEY_NOT_FOUND); + + Page *page = env->page_manager()->fetch(context, + m_btree->get_root_address(), PageManager::kReadOnly); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + // traverse down to the leafs + while (!node->is_leaf()) { + if (node->get_count() == 0) + page = env->page_manager()->fetch(context, node->get_ptr_down(), + PageManager::kReadOnly); + else + page = env->page_manager()->fetch(context, + node->get_record_id(context, node->get_count() - 1), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // and to the last page that is NOT empty + while (node->get_count() == 0) { + if (node->get_left() == 0) + return (HAM_KEY_NOT_FOUND); + page = env->page_manager()->fetch(context, node->get_left(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + } + + // couple this cursor to the largest key in this page + couple_to_page(page, node->get_count() - 1, 0); + + // if duplicates are enabled: move to the end of the duplicate-list + if (!(flags & HAM_SKIP_DUPLICATES)) + m_duplicate_index = node->get_record_count(context, m_coupled_index) - 1; + + return (0); +} + +void +BtreeCursor::couple_to_page(Page *page, uint32_t index) +{ + ham_assert(page != 0); + + if (m_state == kStateCoupled && m_coupled_page != page) + remove_cursor_from_page(m_coupled_page); + + m_coupled_index = index; + m_state = kStateCoupled; + if (m_coupled_page == page) + return; + + m_coupled_page = page; + + // add the cursor to the page + if (page->cursor_list()) { + m_next_in_page = page->cursor_list(); + m_previous_in_page = 0; + page->cursor_list()->m_previous_in_page = this; + } + page->set_cursor_list(this); +} + +void +BtreeCursor::remove_cursor_from_page(Page *page) +{ + BtreeCursor *n, *p; + + if (this == page->cursor_list()) { + n = m_next_in_page; + if (n) + n->m_previous_in_page = 0; + page->set_cursor_list(n); + } + else { + n = m_next_in_page; + p = m_previous_in_page; + if (p) + p->m_next_in_page = n; + if (n) + n->m_previous_in_page = p; + } + + m_coupled_page = 0; + m_next_in_page = 0; + m_previous_in_page = 0; +} + +void +BtreeCursor::uncouple_all_cursors(Context *context, Page *page, int start) +{ + bool skipped = false; + Cursor *cursors = page->cursor_list() + ? page->cursor_list()->get_parent() + : 0; + + while (cursors) { + BtreeCursor *btc = cursors->get_btree_cursor(); + BtreeCursor *next = btc->m_next_in_page; + + // ignore all cursors which are already uncoupled or which are + // coupled to a key in the Transaction + if (btc->m_state == kStateCoupled) { + // skip this cursor if its position is < start + if (btc->m_coupled_index < start) { + cursors = next ? next->m_parent : 0; + skipped = true; + continue; + } + + // otherwise: uncouple the cursor from the page + btc->uncouple_from_page(context); + } + + cursors = next ? next->m_parent : 0; + } + + if (!skipped) + page->set_cursor_list(0); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.h new file mode 100644 index 0000000000..1754371875 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_cursor.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree cursors + * + * A Btree-Cursor is an object which is used to traverse a Btree. + * It is a random access iterator. + * + * Btree-Cursors are used in Cursor structures as defined in cursor.h. But + * some routines use them directly, mostly for performance reasons. Over + * time these layers will be cleaned up and the separation will be improved. + * + * The cursor implementation is very fast. Most of the operations (i.e. + * move previous/next) will not cause any disk access but are O(1) and + * in-memory only. That's because a cursor is directly "coupled" to a + * btree page (Page) that resides in memory. If the page is removed + * from memory (i.e. because the cache decides that it needs to purge the + * cache, or if there's a page split) then the cursor is "uncoupled", and a + * copy of the current key is stored in the cursor. On first access, the + * cursor is "coupled" again and basically performs a normal lookup of the key. + * + * The three states of a BtreeCursor("nil", "coupled", "uncoupled") can be + * retrieved with the method get_state(), and can be modified with + * set_to_nil(), couple_to_page() and uncouple_from_page(). + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_CURSORS_H +#define HAM_BTREE_CURSORS_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "1base/error.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class Cursor; +class BtreeIndex; +class Page; + +// +// The Cursor structure for a b+tree cursor +// +class BtreeCursor +{ + public: + enum { + // Cursor does not point to any key + kStateNil = 0, + // Cursor flag: the cursor is coupled + kStateCoupled = 1, + // Cursor flag: the cursor is uncoupled + kStateUncoupled = 2 + }; + + // Constructor + BtreeCursor(Cursor *parent = 0); + + // Destructor; asserts that the cursor is nil + ~BtreeCursor() { + ham_assert(m_state == kStateNil); + } + + // Returns the parent cursor + // TODO this should be private + Cursor *get_parent() { + return (m_parent); + } + + // Clones another BtreeCursor + void clone(BtreeCursor *other); + + // Returns the cursor's state (kStateCoupled, kStateUncoupled, kStateNil) + uint32_t get_state() const { + return (m_state); + } + + // Reset's the cursor's state and uninitializes it. After this call + // the cursor no longer points to any key. + void set_to_nil(); + + // Returns the page, index in this page and the duplicate index that this + // cursor is coupled to. This is used by Btree functions to optimize + // certain algorithms, i.e. when erasing the current key. + // Asserts that the cursor is coupled. + void get_coupled_key(Page **page, int *index = 0, + int *duplicate_index = 0) const { + ham_assert(m_state == kStateCoupled); + if (page) + *page = m_coupled_page; + if (index) + *index = m_coupled_index; + if (duplicate_index) + *duplicate_index = m_duplicate_index; + } + + // Returns the uncoupled key of this cursor. + // Asserts that the cursor is uncoupled. + ham_key_t *get_uncoupled_key() { + ham_assert(m_state == kStateUncoupled); + return (&m_uncoupled_key); + } + + // Couples the cursor to a key directly in a page. Also sets the + // duplicate index. + void couple_to_page(Page *page, uint32_t index, + int duplicate_index) { + couple_to_page(page, index); + m_duplicate_index = duplicate_index; + } + + // Returns the duplicate index that this cursor points to. + int get_duplicate_index() const { + return (m_duplicate_index); + } + + // Sets the duplicate key we're pointing to + void set_duplicate_index(int duplicate_index) { + m_duplicate_index = duplicate_index; + } + + // Uncouples the cursor + void uncouple_from_page(Context *context); + + // Returns true if a cursor points to this btree key + bool points_to(Context *context, Page *page, int slot); + + // Returns true if a cursor points to this external key + bool points_to(Context *context, ham_key_t *key); + + // Moves the btree cursor to the next page + ham_status_t move_to_next_page(Context *context); + + // Positions the cursor on a key and retrieves the record (if |record| + // is a valid pointer) + ham_status_t find(Context *context, ham_key_t *key, ByteArray *key_arena, + ham_record_t *record, ByteArray *record_arena, + uint32_t flags); + + // Moves the cursor to the first, last, next or previous element + ham_status_t move(Context *context, ham_key_t *key, ByteArray *key_arena, + ham_record_t *record, ByteArray *record_arena, + uint32_t flags); + + // Returns the number of records of the referenced key + int get_record_count(Context *context, uint32_t flags); + + // Overwrite the record of this cursor + void overwrite(Context *context, ham_record_t *record, uint32_t flags); + + // retrieves the record size of the current record + uint64_t get_record_size(Context *context); + + // Closes the cursor + void close() { + set_to_nil(); + } + + // Uncouples all cursors from a page + // This method is called whenever the page is deleted or becomes invalid + static void uncouple_all_cursors(Context *context, Page *page, + int start = 0); + + private: + // Sets the key we're pointing to - if the cursor is coupled. Also + // links the Cursor with |page| (and vice versa). + void couple_to_page(Page *page, uint32_t index); + + // Removes this cursor from a page + void remove_cursor_from_page(Page *page); + + // Couples the cursor to the current page/key + // Asserts that the cursor is uncoupled. After this call the cursor + // will be coupled. + void couple(Context *context); + + // move cursor to the very first key + ham_status_t move_first(Context *context, uint32_t flags); + + // move cursor to the very last key + ham_status_t move_last(Context *context, uint32_t flags); + + // move cursor to the next key + ham_status_t move_next(Context *context, uint32_t flags); + + // move cursor to the previous key + ham_status_t move_previous(Context *context, uint32_t flags); + + // the parent cursor + Cursor *m_parent; + + // The BtreeIndex instance + BtreeIndex *m_btree; + + // "coupled" or "uncoupled" states; coupled means that the + // cursor points into a Page object, which is in + // memory. "uncoupled" means that the cursor has a copy + // of the key on which it points (i.e. because the coupled page was + // flushed to disk and removed from the cache) + int m_state; + + // the id of the duplicate key to which this cursor is coupled + int m_duplicate_index; + + // for coupled cursors: the page we're pointing to + Page *m_coupled_page; + + // ... and the index of the key in that page + int m_coupled_index; + + // for uncoupled cursors: a copy of the key at which we're pointing + ham_key_t m_uncoupled_key; + + // a ByteArray which backs |m_uncoupled_key.data| + ByteArray m_uncoupled_arena; + + // Linked list of cursors which point to the same page + BtreeCursor *m_next_in_page, *m_previous_in_page; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_CURSORS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_erase.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_erase.cc new file mode 100644 index 0000000000..1222cac8fe --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_erase.cc @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3page_manager/page_manager.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_index.h" +#include "3btree/btree_update.h" +#include "3btree/btree_node_proxy.h" +#include "4db/db.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* + * Erases key/value pairs from a btree + */ +class BtreeEraseAction : public BtreeUpdateAction +{ + public: + BtreeEraseAction(BtreeIndex *btree, Context *context, Cursor *cursor, + ham_key_t *key, int duplicate_index = 0, uint32_t flags = 0) + : BtreeUpdateAction(btree, context, cursor + ? cursor->get_btree_cursor() + : 0, duplicate_index), + m_key(key), m_flags(flags) { + if (m_cursor) + m_duplicate_index = m_cursor->get_duplicate_index() + 1; + } + + // This is the entry point for the erase operation + ham_status_t run() { + // Coupled cursor: try to remove the key directly from the page + if (m_cursor) { + if (m_cursor->get_state() == BtreeCursor::kStateCoupled) { + Page *coupled_page; + int coupled_index; + m_cursor->get_coupled_key(&coupled_page, &coupled_index); + + BtreeNodeProxy *node = m_btree->get_node_from_page(coupled_page); + ham_assert(node->is_leaf()); + + // Now try to delete the key. This can require a page split if the + // KeyList is not "delete-stable" (some compressed lists can + // grow when keys are deleted). + try { + remove_entry(coupled_page, 0, coupled_index); + } + catch (Exception &ex) { + if (ex.code != HAM_LIMITS_REACHED) + throw ex; + goto fall_through; + } + // TODO if the page is empty then ask the janitor to clean it up + return (0); + +fall_through: + m_cursor->uncouple_from_page(m_context); + } + + if (m_cursor->get_state() == BtreeCursor::kStateUncoupled) + m_key = m_cursor->get_uncoupled_key(); + } + + return (erase()); + } + + private: + ham_status_t erase() { + // traverse the tree to the leaf, splitting/merging nodes as required + Page *parent; + BtreeStatistics::InsertHints hints; + Page *page = traverse_tree(m_key, hints, &parent); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + // we have reached the leaf; search the leaf for the key + int slot = node->find_exact(m_context, m_key); + if (slot < 0) { + m_btree->get_statistics()->erase_failed(); + return (HAM_KEY_NOT_FOUND); + } + + // remove the key from the leaf + return (remove_entry(page, parent, slot)); + } + + ham_status_t remove_entry(Page *page, Page *parent, int slot) { + LocalDatabase *db = m_btree->get_db(); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + ham_assert(slot >= 0); + ham_assert(slot < (int)node->get_count()); + + // delete the record, but only on leaf nodes! internal nodes don't have + // records; they point to pages instead, and we do not want to delete + // those. + bool has_duplicates_left = false; + if (node->is_leaf()) { + // only delete a duplicate? + if (m_duplicate_index > 0) + node->erase_record(m_context, slot, m_duplicate_index - 1, false, + &has_duplicates_left); + else + node->erase_record(m_context, slot, 0, true, 0); + } + + page->set_dirty(true); + + // still got duplicates left? then adjust all cursors + if (node->is_leaf() && has_duplicates_left && db->cursor_list()) { + Cursor *cursors = db->cursor_list(); + BtreeCursor *btcur = cursors->get_btree_cursor(); + + int duplicate_index = + m_cursor + ? m_cursor->get_duplicate_index() + : m_duplicate_index; + + while (btcur) { + BtreeCursor *next = 0; + if (cursors->get_next()) { + cursors = cursors->get_next(); + next = cursors->get_btree_cursor(); + } + + if (btcur != m_cursor && btcur->points_to(m_context, page, slot)) { + if (btcur->get_duplicate_index() == duplicate_index) + btcur->set_to_nil(); + else if (btcur->get_duplicate_index() > duplicate_index) + btcur->set_duplicate_index(btcur->get_duplicate_index() - 1); + } + btcur = next; + } + // all cursors were adjusted, the duplicate was deleted. return + // to caller! + return (0); + } + + // no duplicates left, the key was deleted; all cursors pointing to + // this key are set to nil, all cursors pointing to a key in the same + // page are adjusted, if necessary + if (node->is_leaf() && !has_duplicates_left && db->cursor_list()) { + Cursor *cursors = db->cursor_list(); + BtreeCursor *btcur = cursors->get_btree_cursor(); + + /* 'nil' every cursor which points to the deleted key, and adjust + * other cursors attached to the same page */ + while (btcur) { + BtreeCursor *cur = btcur; + BtreeCursor *next = 0; + if (cursors->get_next()) { + cursors = cursors->get_next(); + next = cursors->get_btree_cursor(); + } + if (btcur != m_cursor && cur->points_to(m_context, page, slot)) + cur->set_to_nil(); + else if (btcur != m_cursor + && (cur->get_state() & BtreeCursor::kStateCoupled)) { + Page *coupled_page; + int coupled_slot; + cur->get_coupled_key(&coupled_page, &coupled_slot); + if (coupled_page == page && coupled_slot > slot) + cur->uncouple_from_page(m_context); + } + btcur = next; + } + } + + if (has_duplicates_left) + return (0); + + // We've reached the leaf; it's still possible that we have to + // split the page, therefore this case has to be handled + try { + node->erase(m_context, slot); + } + catch (Exception &ex) { + if (ex.code != HAM_LIMITS_REACHED) + throw ex; + + // Split the page in the middle. This will invalidate the |node| pointer + // and the |slot| of the key, therefore restart the whole operation + BtreeStatistics::InsertHints hints = {0}; + split_page(page, parent, m_key, hints); + return (erase()); + } + + return (0); + } + + // the key that is retrieved + ham_key_t *m_key; + + // flags of ham_db_erase() + uint32_t m_flags; +}; + +ham_status_t +BtreeIndex::erase(Context *context, Cursor *cursor, ham_key_t *key, + int duplicate, uint32_t flags) +{ + context->db = get_db(); + + BtreeEraseAction bea(this, context, cursor, key, duplicate, flags); + return (bea.run()); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_find.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_find.cc new file mode 100644 index 0000000000..05c99b5818 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_find.cc @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree searching + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3btree/btree_index.h" +#include "3btree/btree_cursor.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_node_proxy.h" +#include "3page_manager/page_manager.h" +#include "4cursor/cursor.h" +#include "4db/db.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class BtreeFindAction +{ + public: + BtreeFindAction(BtreeIndex *btree, Context *context, Cursor *cursor, + ham_key_t *key, ByteArray *key_arena, + ham_record_t *record, ByteArray *record_arena, + uint32_t flags) + : m_btree(btree), m_context(context), m_cursor(0), m_key(key), + m_record(record), m_flags(flags), m_key_arena(key_arena), + m_record_arena(record_arena) { + if (cursor && cursor->get_btree_cursor()->get_parent()) + m_cursor = cursor->get_btree_cursor(); + } + + ham_status_t run() { + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + Page *page = 0; + int slot = -1; + BtreeNodeProxy *node = 0; + + BtreeStatistics *stats = m_btree->get_statistics(); + BtreeStatistics::FindHints hints = stats->get_find_hints(m_flags); + + if (hints.try_fast_track) { + /* + * see if we get a sure hit within this btree leaf; if not, revert to + * regular scan + * + * As this is a speed-improvement hint re-using recent material, the + * page should still sit in the cache, or we're using old info, which + * should be discarded. + */ + page = env->page_manager()->fetch(m_context, hints.leaf_page_addr, + PageManager::kOnlyFromCache + | PageManager::kReadOnly); + if (page) { + node = m_btree->get_node_from_page(page); + ham_assert(node->is_leaf()); + + uint32_t approx_match; + slot = m_btree->find_leaf(m_context, page, m_key, m_flags, + &approx_match); + + /* + * if we didn't hit a match OR a match at either edge, FAIL. + * A match at one of the edges is very risky, as this can also + * signal a match far away from the current node, so we need + * the full tree traversal then. + */ + if (approx_match || slot <= 0 || slot >= (int)node->get_count() - 1) + slot = -1; + + /* fall through */ + } + } + + uint32_t approx_match = 0; + + if (slot == -1) { + /* load the root page */ + page = env->page_manager()->fetch(m_context, + m_btree->get_root_address(), PageManager::kReadOnly); + + /* now traverse the root to the leaf nodes till we find a leaf */ + node = m_btree->get_node_from_page(page); + while (!node->is_leaf()) { + page = m_btree->find_child(m_context, page, m_key, + PageManager::kReadOnly, 0); + if (!page) { + stats->find_failed(); + return (HAM_KEY_NOT_FOUND); + } + + node = m_btree->get_node_from_page(page); + } + + /* check the leaf page for the key (shortcut w/o approx. matching) */ + if (m_flags == 0) { + slot = node->find_exact(m_context, m_key); + if (slot == -1) { + stats->find_failed(); + return (HAM_KEY_NOT_FOUND); + } + } + + /* check the leaf page for the key (long path w/ approx. matching), + * then fall through */ + slot = m_btree->find_leaf(m_context, page, m_key, m_flags, + &approx_match); + } + + if (slot == -1) { + // find the left sibling + if (node->get_left() > 0) { + page = env->page_manager()->fetch(m_context, node->get_left(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + slot = node->get_count() - 1; + approx_match = BtreeKey::kLower; + } + } + + else if (slot >= (int)node->get_count()) { + // find the right sibling + if (node->get_right() > 0) { + page = env->page_manager()->fetch(m_context, node->get_right(), + PageManager::kReadOnly); + node = m_btree->get_node_from_page(page); + slot = 0; + approx_match = BtreeKey::kGreater; + } + else + slot = -1; + } + + if (slot < 0) { + stats->find_failed(); + return (HAM_KEY_NOT_FOUND); + } + + ham_assert(node->is_leaf()); + + /* set the cursor-position to this key */ + if (m_cursor) { + m_cursor->couple_to_page(page, slot, 0); + } + + /* approx. match: patch the key flags */ + if (approx_match) { + ham_key_set_intflags(m_key, approx_match); + } + + /* no need to load the key if we have an exact match, or if KEY_DONT_LOAD + * is set: */ + if (m_key && approx_match && !(m_flags & Cursor::kSyncDontLoadKey)) { + node->get_key(m_context, slot, m_key_arena, m_key); + } + + if (m_record) { + node->get_record(m_context, slot, m_record_arena, m_record, m_flags); + } + + return (0); + } + + private: + // the current btree + BtreeIndex *m_btree; + + // The caller's Context + Context *m_context; + + // the current cursor + BtreeCursor *m_cursor; + + // the key that is retrieved + ham_key_t *m_key; + + // the record that is retrieved + ham_record_t *m_record; + + // flags of ham_db_find() + uint32_t m_flags; + + // allocator for the key data + ByteArray *m_key_arena; + + // allocator for the record data + ByteArray *m_record_arena; +}; + +ham_status_t +BtreeIndex::find(Context *context, Cursor *cursor, ham_key_t *key, + ByteArray *key_arena, ham_record_t *record, + ByteArray *record_arena, uint32_t flags) +{ + BtreeFindAction bfa(this, context, cursor, key, key_arena, record, + record_arena, flags); + return (bfa.run()); +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_flags.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_flags.h new file mode 100644 index 0000000000..e0d77d8ae0 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_flags.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_FLAGS_H +#define HAM_BTREE_FLAGS_H + +#include "0root/root.h" + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// A helper class wrapping key-related constants into a common namespace. +// This class does not contain any logic. +// +struct BtreeKey +{ + // persisted btree key flags; also used in combination with ham_key_t._flags + enum { + // key is extended with overflow area + kExtendedKey = 0x01, + + // PRO: key is compressed; the original size is stored in the payload + kCompressed = 0x08 + }; + + // flags used with the ham_key_t::_flags (note the underscore - this + // field is for INTERNAL USE!) + // + // Note: these flags should NOT overlap with the persisted flags above! + // + // As these flags NEVER will be persisted, they should be located outside + // the range of a uint16_t, i.e. outside the mask 0x0000ffff. + enum { + // Actual key is lower than the requested key + kLower = 0x00010000, + + // Actual key is greater than the requested key + kGreater = 0x00020000, + + // Actual key is an "approximate match" + kApproximate = (kLower | kGreater) + }; +}; + +// +// A helper class wrapping record-related constants into a common namespace. +// This class does not contain any logic. +// +struct BtreeRecord +{ + enum { + // record size < 8; length is encoded at byte[7] of key->ptr + kBlobSizeTiny = 0x01, + + // record size == 8; record is stored in key->ptr + kBlobSizeSmall = 0x02, + + // record size == 0; key->ptr == 0 + kBlobSizeEmpty = 0x04, + + // key has duplicates in an overflow area; this is the msb of 1 byte; + // the lower bits are the counter for the inline duplicate list + kExtendedDuplicates = 0x80 + }; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_FLAGS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_base.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_base.h new file mode 100644 index 0000000000..d75d2a7be2 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_base.h @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Base class for btree node implementations + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_IMPL_BASE_H +#define HAM_BTREE_IMPL_BASE_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3btree/btree_node.h" +#include "3btree/btree_keys_base.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; + +template<typename KeyList, typename RecordList> +class BaseNodeImpl +{ + public: + // Constructor + BaseNodeImpl(Page *page) + : m_page(page), m_node(PBtreeNode::from_page(page)), + m_estimated_capacity(0), m_keys(page->get_db()), + m_records(page->get_db(), m_node) { + } + + // Returns the estimated page's capacity + size_t estimate_capacity() const { + return (m_estimated_capacity); + } + + // Checks this node's integrity + virtual void check_integrity(Context *context) const { + } + + // Returns a copy of a key and stores it in |dest| + void get_key(Context *context, int slot, ByteArray *arena, + ham_key_t *dest) { + // copy (or assign) the key data + m_keys.get_key(context, slot, arena, dest, true); + } + + // Returns the record size of a key or one of its duplicates + uint64_t get_record_size(Context *context, int slot, int duplicate_index) { + return (m_records.get_record_size(context, slot, duplicate_index)); + } + + // Returns the record counter of a key + int get_record_count(Context *context, int slot) { + return (m_records.get_record_count(context, slot)); + } + + // Returns the full record and stores it in |dest| + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, int duplicate_index) { + // copy the record data + m_records.get_record(context, slot, arena, record, + flags, duplicate_index); + } + + // Updates the record of a key + void set_record(Context *context, int slot, ham_record_t *record, + int duplicate_index, uint32_t flags, + uint32_t *new_duplicate_index) { + // automatically overwrite an existing key unless this is a + // duplicate operation + if ((flags & (HAM_DUPLICATE + | HAM_DUPLICATE + | HAM_DUPLICATE_INSERT_BEFORE + | HAM_DUPLICATE_INSERT_AFTER + | HAM_DUPLICATE_INSERT_FIRST + | HAM_DUPLICATE_INSERT_LAST)) == 0) + flags |= HAM_OVERWRITE; + + m_records.set_record(context, slot, duplicate_index, record, flags, + new_duplicate_index); + } + + // Erases the extended part of a key + void erase_extended_key(Context *context, int slot) { + m_keys.erase_extended_key(context, slot); + } + + // Erases the record + void erase_record(Context *context, int slot, int duplicate_index, + bool all_duplicates) { + m_records.erase_record(context, slot, duplicate_index, all_duplicates); + } + + // Erases a key + void erase(Context *context, int slot) { + size_t node_count = m_node->get_count(); + + m_keys.erase(context, node_count, slot); + m_records.erase(context, node_count, slot); + } + + // Inserts a new key + // + // Most KeyLists first calculate the slot of the new key, then insert + // the key at this slot. Both operations are separate from each other. + // However, compressed KeyLists can overwrite this behaviour and + // combine both calls into one to save performance. + template<typename Cmp> + PBtreeNode::InsertResult insert(Context *context, ham_key_t *key, + uint32_t flags, Cmp &comparator) { + PBtreeNode::InsertResult result(0, 0); + size_t node_count = m_node->get_count(); + + if (node_count == 0) + result.slot = 0; + else if (flags & PBtreeNode::kInsertPrepend) + result.slot = 0; + else if (flags & PBtreeNode::kInsertAppend) + result.slot = node_count; + else { + int cmp; + result.slot = find_lowerbound_impl(context, key, comparator, &cmp); + + /* insert the new key at the beginning? */ + if (result.slot == -1) { + result.slot = 0; + ham_assert(cmp != 0); + } + /* key exists already */ + else if (cmp == 0) { + result.status = HAM_DUPLICATE_KEY; + return (result); + } + /* if the new key is > than the slot key: move to the next slot */ + else if (cmp > 0) + result.slot++; + } + + // Uncouple the cursors. + // + // for custom inserts we have to uncouple all cursors, because the + // KeyList doesn't have access to the cursors in the page. In this + // case result.slot is 0. + if ((int)node_count > result.slot) + BtreeCursor::uncouple_all_cursors(context, m_page, result.slot); + + // make space for 1 additional element. + // only store the key data; flags and record IDs are set by the caller + result = m_keys.insert(context, node_count, key, flags, comparator, + result.slot); + m_records.insert(context, node_count, result.slot); + return (result); + } + + // Compares two keys using the supplied comparator + template<typename Cmp> + int compare(Context *context, const ham_key_t *lhs, + uint32_t rhs, Cmp &cmp) { + if (KeyList::kHasSequentialData) { + return (cmp(lhs->data, lhs->size, m_keys.get_key_data(rhs), + m_keys.get_key_size(rhs))); + } + else { + ham_key_t tmp = {0}; + m_keys.get_key(context, rhs, &m_arena, &tmp, false); + return (cmp(lhs->data, lhs->size, tmp.data, tmp.size)); + } + } + + // Searches the node for the key and returns the slot of this key + template<typename Cmp> + int find_child(Context *context, ham_key_t *key, Cmp &comparator, + uint64_t *precord_id, int *pcmp) { + int slot = find_lowerbound_impl(context, key, comparator, pcmp); + if (precord_id) { + if (slot == -1) + *precord_id = m_node->get_ptr_down(); + else + *precord_id = m_records.get_record_id(slot); + } + return (slot); + } + + // Searches the node for the key and returns the slot of this key + // - only for exact matches! + template<typename Cmp> + int find_exact(Context *context, ham_key_t *key, Cmp &comparator) { + int cmp = 0; + int r = find_exact_impl(context, key, comparator, &cmp); + return (cmp ? -1 : r); + } + + // Splits a node and moves parts of the current node into |other|, starting + // at the |pivot| slot + void split(Context *context, BaseNodeImpl<KeyList, RecordList> *other, + int pivot) { + size_t node_count = m_node->get_count(); + size_t other_node_count = other->m_node->get_count(); + + // + // if a leaf page is split then the pivot element must be inserted in + // the leaf page AND in the internal node. the internal node update + // is handled by the caller. + // + // in internal nodes the pivot element is only propagated to the + // parent node. the pivot element is skipped. + // + if (m_node->is_leaf()) { + m_keys.copy_to(pivot, node_count, other->m_keys, + other_node_count, 0); + m_records.copy_to(pivot, node_count, other->m_records, + other_node_count, 0); + } + else { + m_keys.copy_to(pivot + 1, node_count, other->m_keys, + other_node_count, 0); + m_records.copy_to(pivot + 1, node_count, other->m_records, + other_node_count, 0); + } + } + + // Returns true if the node requires a merge or a shift + bool requires_merge() const { + return (m_node->get_count() <= 3); + } + + // Merges this node with the |other| node + void merge_from(Context *context, + BaseNodeImpl<KeyList, RecordList> *other) { + size_t node_count = m_node->get_count(); + size_t other_node_count = other->m_node->get_count(); + + // shift items from the sibling to this page + if (other_node_count > 0) { + other->m_keys.copy_to(0, other_node_count, m_keys, + node_count, node_count); + other->m_records.copy_to(0, other_node_count, m_records, + node_count, node_count); + } + } + + // Reorganize this node; re-arranges capacities of KeyList and RecordList + // in order to free space and avoid splits + bool reorganize(Context *context, const ham_key_t *key) const { + return (false); + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + metrics->number_of_pages++; + metrics->number_of_keys += node_count; + + BtreeStatistics::update_min_max_avg(&metrics->keys_per_page, node_count); + + m_keys.fill_metrics(metrics, node_count); + m_records.fill_metrics(metrics, node_count); + } + + // Prints a slot to stdout (for debugging) + void print(Context *context, int slot) { + std::stringstream ss; + ss << " "; + m_keys.print(context, slot, ss); + ss << " -> "; + m_records.print(context, slot, ss); + std::cout << ss.str() << std::endl; + } + + // Returns the record id + uint64_t get_record_id(Context *context, int slot) const { + return (m_records.get_record_id(slot)); + } + + // Sets the record id + void set_record_id(Context *context, int slot, uint64_t ptr) { + m_records.set_record_id(slot, ptr); + } + + // The page we're operating on + Page *m_page; + + // The node we're operating on + PBtreeNode *m_node; + + // Capacity of this node (maximum number of key/record pairs that + // can be stored) + size_t m_estimated_capacity; + + // for accessing the keys + KeyList m_keys; + + // for accessing the records + RecordList m_records; + + private: + // Implementation of the find method for lower-bound matches. If there + // is no exact match then the lower bound is returned, and the compare value + // is returned in |*pcmp|. + template<typename Cmp> + int find_lowerbound_impl(Context *context, const ham_key_t *key, + Cmp &comparator, int *pcmp) { + switch ((int)KeyList::kSearchImplementation) { + case BaseKeyList::kBinaryLinear: + return (find_impl_binlin(context, key, comparator, pcmp)); + case BaseKeyList::kCustomSearch: + return (m_keys.find(context, m_node->get_count(), key, + comparator, pcmp)); + default: // BaseKeyList::kBinarySearch + return (find_impl_binary(context, key, comparator, pcmp)); + } + } + + // Implementation of the find method for exact matches. Supports a custom + // search implementation in the KeyList (i.e. for SIMD). + template<typename Cmp> + int find_exact_impl(Context *context, const ham_key_t *key, + Cmp &comparator, int *pcmp) { + switch ((int)KeyList::kSearchImplementation) { + case BaseKeyList::kBinaryLinear: + return (find_impl_binlin(context, key, comparator, pcmp)); + case BaseKeyList::kCustomSearch: + case BaseKeyList::kCustomExactImplementation: + return (m_keys.find(context, m_node->get_count(), key, + comparator, pcmp)); + default: // BaseKeyList::kBinarySearch + return (find_impl_binary(context, key, comparator, pcmp)); + } + } + + // Binary search + template<typename Cmp> + int find_impl_binary(Context *context, const ham_key_t *key, + Cmp &comparator, int *pcmp) { + size_t node_count = m_node->get_count(); + ham_assert(node_count > 0); + + int i, l = 0, r = (int)node_count; + int last = node_count + 1; + int cmp = -1; + + /* repeat till we found the key or the remaining range is so small that + * we rather perform a linear search (which is faster for small ranges) */ + while (r - l > 0) { + /* get the median item; if it's identical with the "last" item, + * we've found the slot */ + i = (l + r) / 2; + + if (i == last) { + ham_assert(i >= 0); + ham_assert(i < (int)node_count); + *pcmp = 1; + return (i); + } + + /* compare it against the key */ + cmp = compare(context, key, i, comparator); + + /* found it? */ + if (cmp == 0) { + *pcmp = cmp; + return (i); + } + /* if the key is bigger than the item: search "to the left" */ + else if (cmp < 0) { + if (r == 0) { + ham_assert(i == 0); + *pcmp = cmp; + return (-1); + } + r = i; + } + /* otherwise search "to the right" */ + else { + last = i; + l = i; + } + } + + *pcmp = cmp; + return (-1); + } + + // Binary search combined with linear search + template<typename Cmp> + int find_impl_binlin(Context *context, const ham_key_t *key, + Cmp &comparator, int *pcmp) { + size_t node_count = m_node->get_count(); + ham_assert(node_count > 0); + + int i, l = 0, r = (int)node_count; + int last = node_count + 1; + int cmp = -1; + + // Run a binary search, but fall back to linear search as soon as + // the remaining range is too small. Sets threshold to 0 if linear + // search is disabled for this KeyList. + int threshold = m_keys.get_linear_search_threshold(); + + /* repeat till we found the key or the remaining range is so small that + * we rather perform a linear search (which is faster for small ranges) */ + while (r - l > threshold) { + /* get the median item; if it's identical with the "last" item, + * we've found the slot */ + i = (l + r) / 2; + + if (i == last) { + ham_assert(i >= 0); + ham_assert(i < (int)node_count); + *pcmp = 1; + return (i); + } + + /* compare it against the key */ + cmp = compare(context, key, i, comparator); + + /* found it? */ + if (cmp == 0) { + *pcmp = cmp; + return (i); + } + /* if the key is bigger than the item: search "to the left" */ + else if (cmp < 0) { + if (r == 0) { + ham_assert(i == 0); + *pcmp = cmp; + return (-1); + } + r = i; + } + /* otherwise search "to the right" */ + else { + last = i; + l = i; + } + } + + // still here? then perform a linear search for the remaining range + ham_assert(r - l <= threshold); + return (m_keys.linear_search(l, r - l, key, comparator, pcmp)); + } + + // A memory arena for various tasks + ByteArray m_arena; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_IMPL_BASE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_default.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_default.h new file mode 100644 index 0000000000..0e7e5618cc --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_default.h @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Btree node layout for variable length keys/records and/or duplicates + * ==================================================================== + * + * This is the default hamsterdb layout. It is chosen for + * 1. variable length keys (with or without duplicates) + * 2. fixed length keys with duplicates + * + * Like the PAX layout implemented in btree_impl_pax.h, the layout implemented + * here stores key data and records separated from each other. This layout is + * more complex, because it is capable of resizing the KeyList and RecordList + * if the node becomes full. + * + * The flat memory layout looks like this: + * + * |Idx1|Idx2|...|Idxn|F1|F2|...|Fn|...(space)...|Key1|Key2|...|Keyn| + * + * ... where Idx<n> are the indices (of slot <n>) + * where F<n> are freelist entries + * where Key<n> is the key data of slot <n>. + * + * In addition, the first few bytes in the node store the following + * information: + * 0 (4 bytes): total capacity of index keys (used keys + freelist) + * 4 (4 bytes): number of used freelist entries + * 8 (4 bytes): offset for the next key at the end of the page + * + * In total, |capacity| contains the number of maximum keys (and index + * entries) that can be stored in the node. The number of used index keys + * is in |m_node->get_count()|. The number of used freelist entries is + * returned by |get_freelist_count()|. The freelist indices start directly + * after the key indices. The key space (with key data and records) starts at + * N * capacity, where |N| is the size of an index entry (the size depends + * on the actual btree configuration, i.e. whether key size is fixed, + * duplicates are used etc). + * + * If records have fixed length then all records of a key (with duplicates) + * are stored next to each other. If they have variable length then each of + * these records is stored with 1 byte for flags: + * Rec1|F1|Rec2|F2|... + * where Recn is an 8 bytes record-ID (offset in the file) OR inline record, + * and F1 is 1 byte for flags (kBlobSizeSmall etc). + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_IMPL_DEFAULT_H +#define HAM_BTREE_IMPL_DEFAULT_H + +#include "0root/root.h" + +#include <algorithm> +#include <iostream> +#include <vector> +#include <map> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_impl_base.h" +#include "3btree/btree_node.h" +#include "3btree/btree_visitor.h" +#include "4env/env_local.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// A BtreeNodeProxy layout which can handle... +// +// 1. fixed length keys w/ duplicates +// 2. variable length keys w/ duplicates +// 3. variable length keys w/o duplicates +// +// Fixed length keys are stored sequentially and reuse the layout from pax. +// Same for the distinct RecordList (if duplicates are disabled). +// +template<typename KeyList, typename RecordList> +class DefaultNodeImpl : public BaseNodeImpl<KeyList, RecordList> +{ + // C++ does not allow access to members of base classes unless they're + // explicitly named; this typedef helps to make the code "less" ugly, + // but it still sucks that i have to use it + // + // http://stackoverflow.com/questions/1120833/derived-template-class-access-to-base-class-member-data + typedef BaseNodeImpl<KeyList, RecordList> P; + + // the type of |this| object + typedef DefaultNodeImpl<KeyList, RecordList> NodeType; + + enum { + // for capacity + kPayloadOffset = 4 + }; + + public: + // Constructor + DefaultNodeImpl(Page *page) + : BaseNodeImpl<KeyList, RecordList>(page) { + initialize(); + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + virtual void check_integrity(Context *context) const { + size_t node_count = P::m_node->get_count(); + if (node_count == 0) + return; + + check_index_integrity(context, node_count); + } + + // Iterates all keys, calls the |visitor| on each + void scan(Context *context, ScanVisitor *visitor, uint32_t start, + bool distinct) { +#ifdef HAM_DEBUG + check_index_integrity(context, P::m_node->get_count()); +#endif + + // a distinct scan over fixed-length keys can be moved to the KeyList + if (KeyList::kSupportsBlockScans && distinct) { + P::m_keys.scan(context, visitor, start, P::m_node->get_count() - start); + return; + } + + // otherwise iterate over the keys, call visitor for each key + ham_key_t key = {0}; + ByteArray arena; + size_t node_count = P::m_node->get_count() - start; + + for (size_t i = start; i < node_count; i++) { + P::m_keys.get_key(context, i, &arena, &key, false); + (*visitor)(key.data, key.size, distinct + ? 1 + : P::get_record_count(context, i)); + } + } + + // Returns the full record and stores it in |dest| + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, int duplicate_index) { +#ifdef HAM_DEBUG + check_index_integrity(context, P::m_node->get_count()); +#endif + P::get_record(context, slot, arena, record, flags, duplicate_index); + } + + // Updates the record of a key + void set_record(Context *context, int slot, ham_record_t *record, + int duplicate_index, uint32_t flags, + uint32_t *new_duplicate_index) { + P::set_record(context, slot, record, duplicate_index, + flags, new_duplicate_index); +#ifdef HAM_DEBUG + check_index_integrity(context, P::m_node->get_count()); +#endif + } + + // Erases the record + void erase_record(Context *context, int slot, int duplicate_index, + bool all_duplicates) { + P::erase_record(context, slot, duplicate_index, all_duplicates); +#ifdef HAM_DEBUG + check_index_integrity(context, P::m_node->get_count()); +#endif + } + + // Erases a key + void erase(Context *context, int slot) { + P::erase(context, slot); +#ifdef HAM_DEBUG + check_index_integrity(context, P::m_node->get_count() - 1); +#endif + } + + // Returns true if |key| cannot be inserted because a split is required. + // This function will try to re-arrange the node in order for the new + // key to fit in. + bool requires_split(Context *context, const ham_key_t *key) { + size_t node_count = P::m_node->get_count(); + + // the node is empty? that's either because nothing was inserted yet, + // or because all keys were erased. For the latter case make sure + // that no garbage remains behind, otherwise it's possible that + // following inserts can fail + if (node_count == 0) { + P::m_records.vacuumize(node_count, true); + P::m_keys.vacuumize(node_count, true); + return (false); + } + + bool keys_require_split = P::m_keys.requires_split(node_count, key); + bool records_require_split = P::m_records.requires_split(node_count); + if (!keys_require_split && !records_require_split) + return (false); + + // first try to vaccumize the lists without rearranging them + if (keys_require_split) { + P::m_keys.vacuumize(node_count, false); + keys_require_split = P::m_keys.requires_split(node_count, key); + } + + if (records_require_split) { + P::m_records.vacuumize(node_count, false); + records_require_split = P::m_records.requires_split(node_count); + } + + if (!keys_require_split && !records_require_split) + return (false); + + // now adjust the ranges and the capacity + if (reorganize(context, key)) { +#ifdef HAM_DEBUG + check_index_integrity(context, node_count); +#endif + return (false); + } + +#ifdef HAM_DEBUG + check_index_integrity(context, node_count); +#endif + + // still here? then there's no way to avoid the split + BtreeIndex *bi = P::m_page->get_db()->btree_index(); + bi->get_statistics()->set_keylist_range_size(P::m_node->is_leaf(), + load_range_size()); + bi->get_statistics()->set_keylist_capacities(P::m_node->is_leaf(), + node_count); + return (true); + } + + // Splits this node and moves some/half of the keys to |other| + void split(Context *context, DefaultNodeImpl *other, int pivot) { + size_t node_count = P::m_node->get_count(); + +#ifdef HAM_DEBUG + check_index_integrity(context, node_count); + ham_assert(other->m_node->get_count() == 0); +#endif + + // make sure that the other node has enough free space + other->initialize(this); + + P::split(context, other, pivot); + + P::m_keys.vacuumize(pivot, true); + P::m_records.vacuumize(pivot, true); + +#ifdef HAM_DEBUG + check_index_integrity(context, pivot); + if (P::m_node->is_leaf()) + other->check_index_integrity(context, node_count - pivot); + else + other->check_index_integrity(context, node_count - pivot - 1); +#endif + } + + // Merges keys from |other| to this node + void merge_from(Context *context, DefaultNodeImpl *other) { + size_t node_count = P::m_node->get_count(); + + P::m_keys.vacuumize(node_count, true); + P::m_records.vacuumize(node_count, true); + + P::merge_from(context, other); + +#ifdef HAM_DEBUG + check_index_integrity(context, node_count + other->m_node->get_count()); +#endif + } + + // Adjusts the size of both lists; either increases it or decreases + // it (in order to free up space for variable length data). + // Returns true if |key| and an additional record can be inserted, or + // false if not; in this case the caller must perform a split. + bool reorganize(Context *context, const ham_key_t *key) { + size_t node_count = P::m_node->get_count(); + + // One of the lists must be resizable (otherwise they would be managed + // by the PaxLayout) + ham_assert(!KeyList::kHasSequentialData + || !RecordList::kHasSequentialData); + + // Retrieve the minimum sizes that both lists require to store their + // data + size_t capacity_hint; + size_t old_key_range_size = load_range_size(); + size_t key_range_size, record_range_size; + size_t required_key_range, required_record_range; + size_t usable_size = usable_range_size(); + required_key_range = P::m_keys.get_required_range_size(node_count) + + P::m_keys.get_full_key_size(key); + required_record_range = P::m_records.get_required_range_size(node_count) + + P::m_records.get_full_record_size(); + + uint8_t *p = P::m_node->get_data(); + p += sizeof(uint32_t); + + // no records? then there's no way to change the ranges. but maybe we + // can increase the capacity + if (required_record_range == 0) { + if (required_key_range > usable_size) + return (false); + P::m_keys.change_range_size(node_count, p, usable_size, + node_count + 5); + return (!P::m_keys.requires_split(node_count, key)); + } + + int remainder = usable_size + - (required_key_range + required_record_range); + if (remainder < 0) + return (false); + + // Now split the remainder between both lists + size_t additional_capacity = remainder + / (P::m_keys.get_full_key_size(0) + + P::m_records.get_full_record_size()); + if (additional_capacity == 0) + return (false); + + key_range_size = required_key_range + additional_capacity + * P::m_keys.get_full_key_size(0); + record_range_size = usable_size - key_range_size; + + ham_assert(key_range_size + record_range_size <= usable_size); + + // Check if the required record space is large enough, and make sure + // there is enough room for a new item + if (key_range_size > usable_size + || record_range_size > usable_size + || key_range_size == old_key_range_size + || key_range_size < required_key_range + || record_range_size < required_record_range + || key_range_size + record_range_size > usable_size) + return (false); + + capacity_hint = get_capacity_hint(key_range_size, record_range_size); + + // sanity check: make sure that the new capacity would be big + // enough for all the keys + if (capacity_hint > 0 && capacity_hint < node_count) + return (false); + + if (capacity_hint == 0) { + BtreeStatistics *bstats = P::m_page->get_db()->btree_index()->get_statistics(); + capacity_hint = bstats->get_keylist_capacities(P::m_node->is_leaf()); + } + + if (capacity_hint < node_count) + capacity_hint = node_count + 1; + + // Get a pointer to the data area and persist the new range size + // of the KeyList + store_range_size(key_range_size); + + // Now update the lists. If the KeyList grows then start with resizing + // the RecordList, otherwise the moved KeyList will overwrite the + // beginning of the RecordList. + if (key_range_size > old_key_range_size) { + P::m_records.change_range_size(node_count, p + key_range_size, + usable_size - key_range_size, + capacity_hint); + P::m_keys.change_range_size(node_count, p, key_range_size, + capacity_hint); + } + // And vice versa if the RecordList grows + else { + P::m_keys.change_range_size(node_count, p, key_range_size, + capacity_hint); + P::m_records.change_range_size(node_count, p + key_range_size, + usable_size - key_range_size, + capacity_hint); + } + + // make sure that the page is flushed to disk + P::m_page->set_dirty(true); + +#ifdef HAM_DEBUG + check_index_integrity(context, node_count); +#endif + + // finally check if the new space is sufficient for the new key + // TODO this shouldn't be required if the check above is implemented + // -> change to an assert, then return true + return (!P::m_records.requires_split(node_count) + && !P::m_keys.requires_split(node_count, key)); + } + + private: + // Initializes the node + void initialize(NodeType *other = 0) { + LocalDatabase *db = P::m_page->get_db(); + size_t usable_size = usable_range_size(); + + // initialize this page in the same way as |other| was initialized + if (other) { + size_t key_range_size = other->load_range_size(); + + // persist the range size + store_range_size(key_range_size); + uint8_t *p = P::m_node->get_data(); + p += sizeof(uint32_t); + + // create the KeyList and RecordList + P::m_keys.create(p, key_range_size); + P::m_records.create(p + key_range_size, + usable_size - key_range_size); + } + // initialize a new page from scratch + else if ((P::m_node->get_count() == 0 + && !(db->get_flags() & HAM_READ_ONLY))) { + size_t key_range_size; + size_t record_range_size; + + // if yes then ask the btree for the default range size (it keeps + // track of the average range size of older pages). + BtreeStatistics *bstats = db->btree_index()->get_statistics(); + key_range_size = bstats->get_keylist_range_size(P::m_node->is_leaf()); + + // no data so far? then come up with a good default + if (key_range_size == 0) { + // no records? then assign the full range to the KeyList + if (P::m_records.get_full_record_size() == 0) { + key_range_size = usable_size; + } + // Otherwise split the range between both lists + else { + size_t capacity = usable_size + / (P::m_keys.get_full_key_size(0) + + P::m_records.get_full_record_size()); + key_range_size = capacity * P::m_keys.get_full_key_size(0); + } + } + + record_range_size = usable_size - key_range_size; + + ham_assert(key_range_size + record_range_size <= usable_size); + + // persist the key range size + store_range_size(key_range_size); + uint8_t *p = P::m_node->get_data(); + p += sizeof(uint32_t); + + // and create the lists + P::m_keys.create(p, key_range_size); + P::m_records.create(p + key_range_size, record_range_size); + + P::m_estimated_capacity = key_range_size + / (size_t)P::m_keys.get_full_key_size(); + } + // open a page; read initialization parameters from persisted storage + else { + size_t key_range_size = load_range_size(); + size_t record_range_size = usable_size - key_range_size; + uint8_t *p = P::m_node->get_data(); + p += sizeof(uint32_t); + + P::m_keys.open(p, key_range_size, P::m_node->get_count()); + P::m_records.open(p + key_range_size, record_range_size, + P::m_node->get_count()); + + P::m_estimated_capacity = key_range_size + / (size_t)P::m_keys.get_full_key_size(); + } + } + + // Try to get a clue about the capacity of the lists; this will help + // those lists with an UpfrontIndex to better arrange their layout + size_t get_capacity_hint(size_t key_range_size, size_t record_range_size) { + if (KeyList::kHasSequentialData) + return (key_range_size / P::m_keys.get_full_key_size()); + if (RecordList::kHasSequentialData && P::m_records.get_full_record_size()) + return (record_range_size / P::m_records.get_full_record_size()); + return (0); + } + + // Checks the integrity of the key- and record-ranges. Throws an exception + // if there's a problem. + void check_index_integrity(Context *context, size_t node_count) const { + P::m_keys.check_integrity(context, node_count); + P::m_records.check_integrity(context, node_count); + } + + // Returns the usable page size that can be used for actually + // storing the data + size_t usable_range_size() const { + return (Page::usable_page_size(P::m_page->get_db()->lenv()->config().page_size_bytes) + - kPayloadOffset + - PBtreeNode::get_entry_offset() + - sizeof(uint32_t)); + } + + // Persists the KeyList's range size + void store_range_size(size_t key_range_size) { + uint8_t *p = P::m_node->get_data(); + *(uint32_t *)p = (uint32_t)key_range_size; + } + + // Load the stored KeyList's range size + size_t load_range_size() const { + uint8_t *p = P::m_node->get_data(); + return (*(uint32_t *)p); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_IMPL_DEFAULT_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_pax.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_pax.h new file mode 100644 index 0000000000..3a87f1c914 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_impl_pax.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Btree node layout for fixed length keys WITHOUT duplicates + * ========================================================== + * + * This layout supports fixed length keys and fixed length records. It does + * not support duplicates and extended keys. Keys and records are always + * inlined, but records can refer to blobs (in this case the "fixed length" + * record is the 8 byte record ID). + * + * Unlike the academic PAX paper, which stored multiple columns in one page, + * hamsterdb stores only one column (= database) in a page, but keys and + * records are separated from each other. The keys (flags + key data) are + * stored in the beginning of the page, the records start somewhere in the + * middle (the exact start position depends on key size, page size and other + * parameters). + * + * This layout's implementation is relatively simple because the offset + * of the key data and record data is easy to calculate since all keys + * and records have the same size. + * + * This separation of keys and records allows a more compact layout and a + * high density of the key data, which better exploits CPU caches and allows + * very tight loops when searching through the keys. + * + * This layout has two incarnations: + * 1. Fixed length keys, fixed length inline records + * -> does not require additional flags + * 2. Fixed length keys, variable length records (8 byte record id) + * -> requires a 1 byte flag per key + * + * The flat memory layout looks like this: + * + * |Flag1|Flag2|...|Flagn|...|Key1|Key2|...|Keyn|...|Rec1|Rec2|...|Recn| + * + * Flags are optional, as described above. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_IMPL_PAX_H +#define HAM_BTREE_IMPL_PAX_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_node.h" +#include "3btree/btree_impl_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// A BtreeNodeProxy layout which stores key data, key flags and +// and the record pointers in a PAX style layout. +// +template<typename KeyList, typename RecordList> +class PaxNodeImpl : public BaseNodeImpl<KeyList, RecordList> +{ + // C++ does not allow access to members of base classes unless they're + // explicitly named; this typedef helps to make the code "less" ugly, + // but it still sucks that i have to use it + // + // http://stackoverflow.com/questions/1120833/derived-template-class-access-to-base-class-member-data + typedef BaseNodeImpl<KeyList, RecordList> P; + + public: + // Constructor + PaxNodeImpl(Page *page) + : BaseNodeImpl<KeyList, RecordList>(page) { + initialize(); + } + + // Iterates all keys, calls the |visitor| on each + void scan(Context *context, ScanVisitor *visitor, uint32_t start, + bool distinct) { + P::m_keys.scan(context, visitor, start, P::m_node->get_count() - start); + } + + // Returns true if |key| cannot be inserted because a split is required + bool requires_split(Context *context, const ham_key_t *key) const { + return (P::m_node->get_count() >= P::m_estimated_capacity); + } + + private: + void initialize() { + uint32_t usable_nodesize + = Page::usable_page_size(P::m_page->get_db()->lenv()->config().page_size_bytes) + - PBtreeNode::get_entry_offset(); + size_t ks = P::m_keys.get_full_key_size(); + size_t rs = P::m_records.get_full_record_size(); + size_t capacity = usable_nodesize / (ks + rs); + + uint8_t *p = P::m_node->get_data(); + if (P::m_node->get_count() == 0) { + P::m_keys.create(&p[0], capacity * ks); + P::m_records.create(&p[capacity * ks], capacity * rs); + } + else { + size_t key_range_size = capacity * ks; + size_t record_range_size = capacity * rs; + + P::m_keys.open(p, key_range_size, P::m_node->get_count()); + P::m_records.open(p + key_range_size, record_range_size, + P::m_node->get_count()); + } + + P::m_estimated_capacity = capacity; + } +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_IMPL_PAX_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.cc new file mode 100644 index 0000000000..a934ba441b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.cc @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "2page/page.h" +#include "3page_manager/page_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_index_factory.h" +#include "3btree/btree_node_proxy.h" +#include "4db/db.h" +#include "4env/env.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +uint64_t BtreeIndex::ms_btree_smo_split = 0; +uint64_t BtreeIndex::ms_btree_smo_merge = 0; +uint64_t BtreeIndex::ms_btree_smo_shift = 0; + +BtreeIndex::BtreeIndex(LocalDatabase *db, PBtreeHeader *btree_header, + uint32_t flags, uint32_t key_type, uint32_t key_size) + : m_db(db), m_key_size(0), m_key_type(key_type), m_rec_size(0), + m_btree_header(btree_header), m_flags(flags), m_root_address(0) +{ + m_leaf_traits = BtreeIndexFactory::create(db, flags, key_type, + key_size, true); + m_internal_traits = BtreeIndexFactory::create(db, flags, key_type, + key_size, false); +} + +void +BtreeIndex::create(Context *context, uint16_t key_type, uint32_t key_size, + uint32_t rec_size) +{ + ham_assert(key_size != 0); + + /* allocate a new root page */ + Page *root = m_db->lenv()->page_manager()->alloc(context, + Page::kTypeBroot, PageManager::kClearWithZero); + + // initialize the new page + PBtreeNode *node = PBtreeNode::from_page(root); + node->set_flags(PBtreeNode::kLeafNode); + + m_key_size = key_size; + m_key_type = key_type; + m_rec_size = rec_size; + m_root_address = root->get_address(); + + flush_descriptor(context); +} + +void +BtreeIndex::open() +{ + uint64_t rootadd; + uint16_t key_size; + uint16_t key_type; + uint32_t flags; + uint32_t rec_size; + + key_size = m_btree_header->get_key_size(); + key_type = m_btree_header->get_key_type(); + rec_size = m_btree_header->get_record_size(); + rootadd = m_btree_header->get_root_address(); + flags = m_btree_header->get_flags(); + + ham_assert(key_size > 0); + ham_assert(rootadd > 0); + + m_root_address = rootadd; + m_key_size = key_size; + m_key_type = key_type; + m_flags = flags; + m_rec_size = rec_size; +} + +void +BtreeIndex::set_record_compression(Context *context, int algo) +{ + m_btree_header->set_record_compression(algo); + flush_descriptor(context); +} + +int +BtreeIndex::get_record_compression() +{ + return (m_btree_header->get_record_compression()); +} + +void +BtreeIndex::set_key_compression(Context *context, int algo) +{ + m_btree_header->set_key_compression(algo); + flush_descriptor(context); +} + +int +BtreeIndex::get_key_compression() +{ + return (m_btree_header->get_key_compression()); +} + +void +BtreeIndex::flush_descriptor(Context *context) +{ + if (m_db->get_flags() & HAM_READ_ONLY) + return; + + m_btree_header->set_dbname(m_db->name()); + m_btree_header->set_key_size(get_key_size()); + m_btree_header->set_rec_size(get_record_size()); + m_btree_header->set_key_type(get_key_type()); + m_btree_header->set_root_address(get_root_address()); + m_btree_header->set_flags(get_flags()); +} + +Page * +BtreeIndex::find_child(Context *context, Page *page, const ham_key_t *key, + uint32_t page_manager_flags, int *idxptr) +{ + BtreeNodeProxy *node = get_node_from_page(page); + + // make sure that we're not in a leaf page, and that the + // page is not empty + ham_assert(node->get_ptr_down() != 0); + + uint64_t record_id; + int slot = node->find_child(context, (ham_key_t *)key, &record_id); + + if (idxptr) + *idxptr = slot; + + return (m_db->lenv()->page_manager()->fetch(context, + record_id, page_manager_flags)); +} + +int +BtreeIndex::find_leaf(Context *context, Page *page, ham_key_t *key, + uint32_t flags, uint32_t *approx_match) +{ + *approx_match = 0; + + /* ensure the approx flag is NOT set by anyone yet */ + BtreeNodeProxy *node = get_node_from_page(page); + if (node->get_count() == 0) + return (-1); + + int cmp; + int slot = node->find_child(context, key, 0, &cmp); + + /* successfull match */ + if (cmp == 0 && (flags == 0 || flags & HAM_FIND_EXACT_MATCH)) + return (slot); + + /* approx. matching: smaller key is required */ + if (flags & HAM_FIND_LT_MATCH) { + if (cmp == 0 && (flags & HAM_FIND_GT_MATCH)) { + *approx_match = BtreeKey::kLower; + return (slot + 1); + } + + if (slot < 0 && (flags & HAM_FIND_GT_MATCH)) { + *approx_match = BtreeKey::kGreater; + return (0); + } + *approx_match = BtreeKey::kLower; + if (cmp <= 0) + return (slot - 1); + return (slot); + } + + /* approx. matching: greater key is required */ + if (flags & HAM_FIND_GT_MATCH) { + *approx_match = BtreeKey::kGreater; + return (slot + 1); + } + + return (cmp ? -1 : slot); +} + +// +// visitor object for estimating / counting the number of keys +/// +class CalcKeysVisitor : public BtreeVisitor { + public: + CalcKeysVisitor(LocalDatabase *db, bool distinct) + : m_db(db), m_distinct(distinct), m_count(0) { + } + + virtual bool is_read_only() const { + return (true); + } + + virtual void operator()(Context *context, BtreeNodeProxy *node) { + size_t node_count = node->get_count(); + + if (m_distinct + || (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) == 0) { + m_count += node_count; + return; + } + + for (size_t i = 0; i < node_count; i++) + m_count += node->get_record_count(context, i); + } + + uint64_t get_result() const { + return (m_count); + } + + private: + LocalDatabase *m_db; + bool m_distinct; + uint64_t m_count; +}; + +uint64_t +BtreeIndex::count(Context *context, bool distinct) +{ + CalcKeysVisitor visitor(m_db, distinct); + visit_nodes(context, visitor, false); + return (visitor.get_result()); +} + +// +// visitor object to free all allocated blobs +/// +class FreeBlobsVisitor : public BtreeVisitor { + public: + virtual void operator()(Context *context, BtreeNodeProxy *node) { + node->remove_all_entries(context); + } + + virtual bool is_read_only() const { + return (false); + } +}; + +void +BtreeIndex::release(Context *context) +{ + FreeBlobsVisitor visitor; + visit_nodes(context, visitor, true); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.h new file mode 100644 index 0000000000..f325f7915f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index.h @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_INDEX_H +#define HAM_BTREE_INDEX_H + +#include "0root/root.h" + +#include <algorithm> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/abi.h" +#include "1base/dynamic_array.h" +#include "3btree/btree_cursor.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_node.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; + +#include "1base/packstart.h" + +// +// The persistent btree index descriptor. This structure manages the +// persistent btree metadata. +// +HAM_PACK_0 class HAM_PACK_1 PBtreeHeader +{ + public: + PBtreeHeader() { + memset(this, 0, sizeof(*this)); + } + + // Returns the database name + uint16_t get_dbname() const { + return (m_dbname); + } + + // Sets the database name + void set_dbname(uint16_t name) { + m_dbname = name; + } + + // Returns the btree's max. key_size + size_t get_key_size() const { + return (m_key_size); + } + + // Sets the btree's max. key_size + void set_key_size(uint16_t key_size) { + m_key_size = key_size; + } + + // Returns the record size (or 0 if none was specified) + uint32_t get_record_size() const { + return (m_rec_size); + } + + // Sets the record size + void set_rec_size(uint32_t rec_size) { + m_rec_size = rec_size; + } + + // Returns the btree's key type + uint16_t get_key_type() const { + return (m_key_type); + } + + // Sets the btree's key type + void set_key_type(uint16_t key_type) { + m_key_type = key_type; + } + + // Returns the address of the btree's root page. + uint64_t get_root_address() const { + return (m_root_address); + } + + // Sets the address of the btree's root page. + void set_root_address(uint64_t root_address) { + m_root_address = root_address; + } + + // Returns the btree's flags + uint32_t get_flags() const { + return (m_flags); + } + + // Sets the btree's flags + void set_flags(uint32_t flags) { + m_flags = flags; + } + + // PRO: Returns the record compression + uint8_t get_record_compression() const { + return (m_compression >> 4); + } + + // PRO: Sets the record compression + void set_record_compression(int algorithm) { + m_compression |= algorithm << 4; + } + + // PRO: Returns the key compression + uint8_t get_key_compression() const { + return (m_compression & 0xf); + } + + // PRO: Sets the key compression + void set_key_compression(int algorithm) { + m_compression |= algorithm & 0xf; + } + + private: + // address of the root-page + uint64_t m_root_address; + + // flags for this database + uint32_t m_flags; + + // The name of the database + uint16_t m_dbname; + + // key size used in the pages + uint16_t m_key_size; + + // key type + uint16_t m_key_type; + + // PRO: for storing key and record compression algorithm */ + uint8_t m_compression; + + // reserved + uint8_t m_reserved1; + + // the record size + uint32_t m_rec_size; + +} HAM_PACK_2; + +#include "1base/packstop.h" + +struct Context; +class LocalDatabase; +class BtreeNodeProxy; +struct PDupeEntry; +struct BtreeVisitor; + +// +// Abstract base class, overwritten by a templated version +// +class BtreeIndexTraits +{ + public: + // virtual destructor + virtual ~BtreeIndexTraits() { } + + // Compares two keys + // Returns -1, 0, +1 or higher positive values are the result of a + // successful key comparison (0 if both keys match, -1 when + // LHS < RHS key, +1 when LHS > RHS key). + virtual int compare_keys(LocalDatabase *db, ham_key_t *lhs, + ham_key_t *rhs) const = 0; + + // Returns the class name (for testing) + virtual std::string test_get_classname() const = 0; + + // Implementation of get_node_from_page() + virtual BtreeNodeProxy *get_node_from_page_impl(Page *page) const = 0; +}; + +// +// The Btree. Derived by BtreeIndexImpl, which uses template policies to +// define the btree node layout. +// +class BtreeIndex +{ + public: + enum { + // for get_node_from_page(): Page is a leaf + kLeafPage = 1, + + // for get_node_from_page(): Page is an internal node + kInternalPage = 2 + }; + + // Constructor; creates and initializes a new btree + BtreeIndex(LocalDatabase *db, PBtreeHeader *btree_header, + uint32_t flags, uint32_t key_type, uint32_t key_size); + + ~BtreeIndex() { + delete m_leaf_traits; + m_leaf_traits = 0; + delete m_internal_traits; + m_internal_traits = 0; + } + + // Returns the database pointer + LocalDatabase *get_db() { + return (m_db); + } + + // Returns the database pointer + LocalDatabase *get_db() const { + return (m_db); + } + + // Returns the internal key size + size_t get_key_size() const { + return (m_key_size); + } + + // Returns the record size + size_t get_record_size() const { + return (m_rec_size); + } + + // Returns the internal key type + uint16_t get_key_type() const { + return (m_key_type); + } + + // Returns the address of the root page + uint64_t get_root_address() const { + return (m_root_address); + } + + // Returns the btree flags + uint32_t get_flags() const { + return (m_flags); + } + + // Creates and initializes the btree + // + // This function is called after the ham_db_t structure was allocated + // and the file was opened + void create(Context *context, uint16_t key_type, uint32_t key_size, + uint32_t rec_size); + + // Opens and initializes the btree + // + // This function is called after the ham_db_t structure was allocated + // and the file was opened + void open(); + + // Sets the record compression algorithm + void set_record_compression(Context *context, int algo); + + // Returns the record compression algorithm + int get_record_compression(); + + // Sets the key compression algorithm + void set_key_compression(Context *context, int algo); + + // Returns the key compression algorithm + int get_key_compression(); + + // Lookup a key in the index (ham_db_find) + ham_status_t find(Context *context, Cursor *cursor, ham_key_t *key, + ByteArray *key_arena, ham_record_t *record, + ByteArray *record_arena, uint32_t flags); + + // Inserts (or updates) a key/record in the index (ham_db_insert) + ham_status_t insert(Context *context, Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + + // Erases a key/record from the index (ham_db_erase). + // If |duplicate_index| is 0 then all duplicates are erased, otherwise only + // the specified duplicate is erased. + ham_status_t erase(Context *context, Cursor *cursor, ham_key_t *key, + int duplicate_index, uint32_t flags); + + // Iterates over the whole index and calls |visitor| on every node + void visit_nodes(Context *context, BtreeVisitor &visitor, + bool visit_internal_nodes); + + // Checks the integrity of the btree (ham_db_check_integrity) + void check_integrity(Context *context, uint32_t flags); + + // Counts the keys in the btree + uint64_t count(Context *context, bool distinct); + + // Erases all records, overflow areas, extended keys etc from the index; + // used to avoid memory leaks when closing in-memory Databases and to + // clean up when deleting on-disk Databases. + void release(Context *context); + + // Compares two keys + // Returns -1, 0, +1 or higher positive values are the result of a + // successful key comparison (0 if both keys match, -1 when + // LHS < RHS key, +1 when LHS > RHS key). + int compare_keys(ham_key_t *lhs, ham_key_t *rhs) const { + return (m_leaf_traits->compare_keys(m_db, lhs, rhs)); + } + + // Returns a BtreeNodeProxy for a Page + BtreeNodeProxy *get_node_from_page(Page *page) { + if (page->get_node_proxy()) + return (page->get_node_proxy()); + + BtreeNodeProxy *proxy; + PBtreeNode *node = PBtreeNode::from_page(page); + if (node->is_leaf()) + proxy = get_leaf_node_from_page_impl(page); + else + proxy = get_internal_node_from_page_impl(page); + + page->set_node_proxy(proxy); + return (proxy); + } + + // Returns the usage metrics + static void fill_metrics(ham_env_metrics_t *metrics) { + metrics->btree_smo_split = ms_btree_smo_split; + metrics->btree_smo_merge = ms_btree_smo_merge; + metrics->extended_keys = Globals::ms_extended_keys; + metrics->extended_duptables = Globals::ms_extended_duptables; + metrics->key_bytes_before_compression + = Globals::ms_bytes_before_compression; + metrics->key_bytes_after_compression + = Globals::ms_bytes_after_compression; + } + + // Returns the btree usage statistics + BtreeStatistics *get_statistics() { + return (&m_statistics); + } + + // Returns the class name (for testing) + std::string test_get_classname() const { + return (m_leaf_traits->test_get_classname()); + } + + private: + friend class BtreeUpdateAction; + friend class BtreeCheckAction; + friend class BtreeEnumAction; + friend class BtreeEraseAction; + friend class BtreeFindAction; + friend class BtreeInsertAction; + friend class BtreeCursor; + friend struct MiscFixture; + friend struct BtreeKeyFixture; + friend struct BtreeCursorFixture; + friend struct DbFixture; + friend struct DuplicateFixture; + + // Implementation of get_node_from_page() (for leaf nodes) + BtreeNodeProxy *get_leaf_node_from_page_impl(Page *page) const { + return (m_leaf_traits->get_node_from_page_impl(page)); + } + + // Implementation of get_node_from_page() (for internal nodes) + BtreeNodeProxy *get_internal_node_from_page_impl(Page *page) const { + return (m_internal_traits->get_node_from_page_impl(page)); + } + + // Sets the address of the root page + void set_root_address(Context *context, uint64_t address) { + m_root_address = address; + flush_descriptor(context); + } + + // Flushes the PBtreeHeader to the Environment's header page + void flush_descriptor(Context *context); + + // Searches |parent| page for key |key| and returns the child + // page in |child|. + // + // |page_manager_flags| are forwarded to PageManager::fetch. + // + // if |idxptr| is a valid pointer then it will return the anchor index + // of the loaded page. + Page *find_child(Context *context, Page *parent, const ham_key_t *key, + uint32_t page_manager_flags, int *idxptr); + + // Searches a leaf node for a key. + // + // !!! + // only works with leaf nodes!! + // + // Returns the index of the key, or -1 if the key was not found, or + // another negative status code value when an unexpected error occurred. + int find_leaf(Context *context, Page *page, ham_key_t *key, uint32_t flags, + uint32_t *approx_match); + + // pointer to the database object + LocalDatabase *m_db; + + // the Traits class wrapping the template parameters (factory for + // leaf nodes) + BtreeIndexTraits *m_leaf_traits; + + // the Traits class wrapping the template parameters (factory for + // internal nodes) + BtreeIndexTraits *m_internal_traits; + + // the key_size of this btree index + uint16_t m_key_size; + + // the key_type of this btree index + uint16_t m_key_type; + + // the record size (or 0 if none was specified) + uint32_t m_rec_size; + + // the index of the PBtreeHeader in the Environment's header page + PBtreeHeader *m_btree_header; + + // the persistent flags of this btree index + uint32_t m_flags; + + // address of the root-page + uint64_t m_root_address; + + // the btree statistics + BtreeStatistics m_statistics; + + // usage metrics - number of page splits + static uint64_t ms_btree_smo_split; + + // usage metrics - number of page merges + static uint64_t ms_btree_smo_merge; + + // usage metrics - number of page shifts + static uint64_t ms_btree_smo_shift; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_INDEX_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index_factory.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index_factory.h new file mode 100644 index 0000000000..49d1ea8189 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_index_factory.h @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_INDEX_FACTORY_H +#define HAM_BTREE_INDEX_FACTORY_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3btree/btree_index.h" +#include "3btree/btree_impl_default.h" +#include "3btree/btree_impl_pax.h" +#include "3btree/btree_keys_pod.h" +#include "3btree/btree_keys_binary.h" +#include "3btree/btree_keys_varlen.h" +#include "3btree/btree_records_default.h" +#include "3btree/btree_records_inline.h" +#include "3btree/btree_records_internal.h" +#include "3btree/btree_records_duplicate.h" +#include "3btree/btree_node_proxy.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// A specialied Traits class using template parameters +// +template<class NodeLayout, class Comparator> +class BtreeIndexTraitsImpl : public BtreeIndexTraits +{ + public: + // Compares two keys + // Returns -1, 0, +1 or higher positive values are the result of a + // successful key comparison (0 if both keys match, -1 when + // LHS < RHS key, +1 when LHS > RHS key). + virtual int compare_keys(LocalDatabase *db, ham_key_t *lhs, + ham_key_t *rhs) const { + Comparator cmp(db); + return (cmp(lhs->data, lhs->size, rhs->data, rhs->size)); + } + + // Returns the class name (for testing) + virtual std::string test_get_classname() const { + return (get_classname(*this)); + } + + // Implementation of get_node_from_page() + virtual BtreeNodeProxy *get_node_from_page_impl(Page *page) const { + return (new BtreeNodeProxyImpl<NodeLayout, Comparator>(page)); + } +}; + +// +// A BtreeIndexFactory creates BtreeIndexProxy objects depending on the +// Database configuration +// +struct BtreeIndexFactory +{ + static BtreeIndexTraits *create(LocalDatabase *db, uint32_t flags, + uint16_t key_type, uint16_t key_size, bool is_leaf) { + bool inline_records = (is_leaf && (flags & HAM_FORCE_RECORDS_INLINE)); + bool fixed_keys = (key_size != HAM_KEY_SIZE_UNLIMITED); + bool use_duplicates = (flags & HAM_ENABLE_DUPLICATES) != 0; + + switch (key_type) { + // 8bit unsigned integer + case HAM_TYPE_UINT8: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint8_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint8_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint8_t>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<uint8_t> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint8_t>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<uint8_t> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint8_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint8_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint8_t>, + PaxLayout::InlineRecordList>, + NumericCompare<uint8_t> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint8_t>, + PaxLayout::DefaultRecordList>, + NumericCompare<uint8_t> >()); + } + // 16bit unsigned integer + case HAM_TYPE_UINT16: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint16_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint16_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint16_t>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<uint16_t> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint16_t>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<uint16_t> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint16_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint16_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint16_t>, + PaxLayout::InlineRecordList>, + NumericCompare<uint16_t> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint16_t>, + PaxLayout::DefaultRecordList>, + NumericCompare<uint16_t> >()); + } + // 32bit unsigned integer + case HAM_TYPE_UINT32: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint32_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint32_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint32_t>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<uint32_t> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint32_t>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<uint32_t> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint32_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint32_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint32_t>, + PaxLayout::InlineRecordList>, + NumericCompare<uint32_t> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint32_t>, + PaxLayout::DefaultRecordList>, + NumericCompare<uint32_t> >()); + } + // 64bit unsigned integer + case HAM_TYPE_UINT64: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint64_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint64_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint64_t>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<uint64_t> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<uint64_t>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<uint64_t> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<uint64_t>, + PaxLayout::InternalRecordList>, + NumericCompare<uint64_t> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint64_t>, + PaxLayout::InlineRecordList>, + NumericCompare<uint64_t> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<uint64_t>, + PaxLayout::DefaultRecordList>, + NumericCompare<uint64_t> >()); + } + // 32bit float + case HAM_TYPE_REAL32: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<float>, + PaxLayout::InternalRecordList>, + NumericCompare<float> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<float>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<float> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<float>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<float> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<float>, + PaxLayout::InternalRecordList>, + NumericCompare<float> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<float>, + PaxLayout::InlineRecordList>, + NumericCompare<float> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<float>, + PaxLayout::DefaultRecordList>, + NumericCompare<float> >()); + } + // 64bit double + case HAM_TYPE_REAL64: + if (use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::PodKeyList<double>, + PaxLayout::InternalRecordList>, + NumericCompare<double> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<double>, + DefLayout::DuplicateInlineRecordList>, + NumericCompare<double> >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::PodKeyList<double>, + DefLayout::DuplicateDefaultRecordList>, + NumericCompare<double> >()); + } + else { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<double>, + PaxLayout::InternalRecordList>, + NumericCompare<double> >()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<double>, + PaxLayout::InlineRecordList>, + NumericCompare<double> >()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::PodKeyList<double>, + PaxLayout::DefaultRecordList>, + NumericCompare<double> >()); + } + // Callback function provided by user? + case HAM_TYPE_CUSTOM: + // Fixed keys, no duplicates + if (fixed_keys && !use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InternalRecordList>, + CallbackCompare>()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InlineRecordList>, + CallbackCompare>()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::DefaultRecordList>, + CallbackCompare>()); + } + // Fixed keys WITH duplicates + if (fixed_keys && use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InternalRecordList>, + CallbackCompare >()); + if (inline_records) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::BinaryKeyList, + DefLayout::DuplicateInlineRecordList>, + CallbackCompare >()); + else + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::BinaryKeyList, + DefLayout::DuplicateDefaultRecordList>, + CallbackCompare >()); + } + // Variable keys with or without duplicates + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::InternalRecordList>, + CallbackCompare >()); + if (inline_records && !use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::InlineRecordList>, + CallbackCompare >()); + if (inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + DefLayout::DuplicateInlineRecordList>, + CallbackCompare >()); + if (!inline_records && !use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::DefaultRecordList>, + CallbackCompare >()); + if (!inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + DefLayout::DuplicateDefaultRecordList>, + CallbackCompare >()); + ham_assert(!"shouldn't be here"); + // BINARY is the default: + case HAM_TYPE_BINARY: + // Fixed keys, no duplicates + if (fixed_keys && !use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InternalRecordList>, + FixedSizeCompare>()); + if (inline_records) + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InlineRecordList>, + FixedSizeCompare>()); + else + return (new BtreeIndexTraitsImpl + <PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::DefaultRecordList>, + FixedSizeCompare>()); + } + // fixed keys with duplicates + if (fixed_keys && use_duplicates) { + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + PaxNodeImpl<PaxLayout::BinaryKeyList, + PaxLayout::InternalRecordList>, + FixedSizeCompare >()); + if (inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::BinaryKeyList, + DefLayout::DuplicateInlineRecordList>, + FixedSizeCompare >()); + if (!inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<PaxLayout::BinaryKeyList, + DefLayout::DuplicateDefaultRecordList>, + FixedSizeCompare >()); + } + // variable length keys, with and without duplicates + if (!is_leaf) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::InternalRecordList>, + VariableSizeCompare >()); + if (inline_records && !use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::InlineRecordList>, + VariableSizeCompare >()); + if (inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + DefLayout::DuplicateInlineRecordList>, + VariableSizeCompare >()); + if (!inline_records && !use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + PaxLayout::DefaultRecordList>, + VariableSizeCompare >()); + if (!inline_records && use_duplicates) + return (new BtreeIndexTraitsImpl< + DefaultNodeImpl<DefLayout::VariableLengthKeyList, + DefLayout::DuplicateDefaultRecordList>, + VariableSizeCompare >()); + ham_assert(!"shouldn't be here"); + default: + break; + } + + ham_assert(!"shouldn't be here"); + return (0); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_INDEX_FACTORY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_insert.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_insert.cc new file mode 100644 index 0000000000..7dac8365d7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_insert.cc @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree inserting + */ + +#include "0root/root.h" + +#include <string.h> +#include <algorithm> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3page_manager/page_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_node_proxy.h" +#include "3btree/btree_cursor.h" +#include "3btree/btree_update.h" +#include "4cursor/cursor.h" +#include "4db/db.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace std; + +namespace hamsterdb { + +class BtreeInsertAction : public BtreeUpdateAction +{ + public: + BtreeInsertAction(BtreeIndex *btree, Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags) + : BtreeUpdateAction(btree, context, cursor + ? cursor->get_btree_cursor() + : 0, 0), + m_key(key), m_record(record), m_flags(flags) { + if (m_cursor) + m_duplicate_index = m_cursor->get_duplicate_index(); + } + + // This is the entry point for the actual insert operation + ham_status_t run() { + BtreeStatistics *stats = m_btree->get_statistics(); + + m_hints = stats->get_insert_hints(m_flags); + + ham_assert((m_hints.flags & (HAM_DUPLICATE_INSERT_BEFORE + | HAM_DUPLICATE_INSERT_AFTER + | HAM_DUPLICATE_INSERT_FIRST + | HAM_DUPLICATE_INSERT_LAST)) + ? (m_hints.flags & HAM_DUPLICATE) + : 1); + + /* + * append the key? append_or_prepend_key() will try to append or + * prepend the key; if this fails because the key is NOT the largest + * (or smallest) key in the database or because the current page is + * already full, it will remove the HINT_APPEND (or HINT_PREPEND) + * flag and call insert() + */ + ham_status_t st; + if (m_hints.leaf_page_addr + && (m_hints.flags & HAM_HINT_APPEND + || m_hints.flags & HAM_HINT_PREPEND)) + st = append_or_prepend_key(); + else + st = insert(); + + if (st == HAM_LIMITS_REACHED) + st = insert(); + + if (st) + stats->insert_failed(); + else { + if (m_hints.processed_leaf_page) + stats->insert_succeeded(m_hints.processed_leaf_page, + m_hints.processed_slot); + } + + return (st); + } + + private: + // Appends a key at the "end" of the btree, or prepends it at the + // "beginning" + ham_status_t append_or_prepend_key() { + Page *page; + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + bool force_append = false; + bool force_prepend = false; + + /* + * see if we get this btree leaf; if not, revert to regular scan + * + * As this is a speed-improvement hint re-using recent material, the page + * should still sit in the cache, or we're using old info, which should + * be discarded. + */ + page = env->page_manager()->fetch(m_context, m_hints.leaf_page_addr, + PageManager::kOnlyFromCache); + /* if the page is not in cache: do a regular insert */ + if (!page) + return (insert()); + + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + ham_assert(node->is_leaf()); + + /* + * if the page is already full OR this page is not the right-most page + * when we APPEND or the left-most node when we PREPEND + * OR the new key is not the highest key: perform a normal insert + */ + if ((m_hints.flags & HAM_HINT_APPEND && node->get_right() != 0) + || (m_hints.flags & HAM_HINT_PREPEND && node->get_left() != 0) + || node->requires_split(m_context, m_key)) + return (insert()); + + /* + * if the page is not empty: check if we append the key at the end/start + * (depending on the flags), or if it's actually inserted in the middle. + */ + if (node->get_count() != 0) { + if (m_hints.flags & HAM_HINT_APPEND) { + int cmp_hi = node->compare(m_context, m_key, node->get_count() - 1); + /* key is at the end */ + if (cmp_hi > 0) { + ham_assert(node->get_right() == 0); + force_append = true; + } + } + + if (m_hints.flags & HAM_HINT_PREPEND) { + int cmp_lo = node->compare(m_context, m_key, 0); + /* key is at the start of page */ + if (cmp_lo < 0) { + ham_assert(node->get_left() == 0); + force_prepend = true; + } + } + } + + /* OK - we're really appending/prepending the new key. */ + if (force_append || force_prepend) + return (insert_in_page(page, m_key, m_record, m_hints, + force_prepend, force_append)); + + /* otherwise reset the hints because they are no longer valid */ + m_hints.flags &= ~HAM_HINT_APPEND; + m_hints.flags &= ~HAM_HINT_PREPEND; + return (insert()); + } + + ham_status_t insert() { + // traverse the tree till a leaf is reached + Page *parent; + Page *page = traverse_tree(m_key, m_hints, &parent); + + // We've reached the leaf; it's still possible that we have to + // split the page, therefore this case has to be handled + ham_status_t st = insert_in_page(page, m_key, m_record, m_hints); + if (st == HAM_LIMITS_REACHED) { + page = split_page(page, parent, m_key, m_hints); + return (insert_in_page(page, m_key, m_record, m_hints)); + } + return (st); + } + + // the key that is inserted + ham_key_t *m_key; + + // the record that is inserted + ham_record_t *m_record; + + // flags of ham_db_insert() + uint32_t m_flags; + + // statistical hints for this operation + BtreeStatistics::InsertHints m_hints; +}; + +ham_status_t +BtreeIndex::insert(Context *context, Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + context->db = get_db(); + + BtreeInsertAction bia(this, context, cursor, key, record, flags); + return (bia.run()); +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_base.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_base.h new file mode 100644 index 0000000000..da5804ad04 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_base.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Base class for KeyLists + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_KEYS_BASE_H +#define HAM_BTREE_KEYS_BASE_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct BaseKeyList +{ + enum { + // This KeyList cannot reduce its capacity in order to release storage + kCanReduceCapacity = 0, + + // This KeyList uses binary search combined with linear search + kBinaryLinear, + + // This KeyList has a custom search implementation + kCustomSearch, + + // This KeyList has a custom search implementation for exact matches + // *only* + kCustomExactImplementation, + + // This KeyList uses binary search (this is the default) + kBinarySearch, + + // Specifies the search implementation: + kSearchImplementation = kBinarySearch, + + // This KeyList does NOT have a custom insert implementation + kCustomInsert = 0, + }; + + BaseKeyList() + : m_range_size(0) { + } + + // Erases the extended part of a key; nothing to do here + void erase_extended_key(Context *context, int slot) const { + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + void check_integrity(Context *context, size_t node_count) const { + } + + // Rearranges the list + void vacuumize(size_t node_count, bool force) const { + } + + // Finds a key + template<typename Cmp> + int find(Context *, size_t node_count, const ham_key_t *key, Cmp &comparator, + int *pcmp) { + ham_assert(!"shouldn't be here"); + return (0); + } + + // Returns the threshold when switching from binary search to + // linear search. Disabled by default + size_t get_linear_search_threshold() const { + return ((size_t)-1); + } + + // Performs a linear search in a given range between |start| and + // |start + length|. Disabled by default. + template<typename Cmp> + int linear_search(size_t start, size_t length, const ham_key_t *hkey, + Cmp &comparator, int *pcmp) { + ham_assert(!"shouldn't be here"); + throw Exception(HAM_INTERNAL_ERROR); + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BtreeStatistics::update_min_max_avg(&metrics->keylist_ranges, m_range_size); + } + + // The size of the range (in bytes) + size_t m_range_size; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_KEYS_BASE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_binary.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_binary.h new file mode 100644 index 0000000000..faea959ec5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_binary.h @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Fixed length KeyList for binary data + * + * This KeyList stores binary keys of fixed length size. It is implemented + * as a plain C array of type uint8_t[]. It has fast random access, i.e. + * key #N starts at data[N * keysize]. + * + * This KeyList cannot be resized. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_KEYS_BINARY_H +#define HAM_BTREE_KEYS_BINARY_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3btree/btree_node.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_keys_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The template classes in this file are wrapped in a separate namespace +// to avoid naming clashes with btree_impl_default.h +// +namespace PaxLayout { + +// +// Same as the PodKeyList, but for binary arrays of fixed length +// +class BinaryKeyList : public BaseKeyList +{ + public: + enum { + // A flag whether this KeyList has sequential data + kHasSequentialData = 1, + + // A flag whether this KeyList supports the scan() call + kSupportsBlockScans = 1, + + // This KeyList uses binary search in combination with linear search + kSearchImplementation = kBinaryLinear, + }; + + // Constructor + BinaryKeyList(LocalDatabase *db) + : m_data(0) { + m_key_size = db->config().key_size; + ham_assert(m_key_size != 0); + } + + // Creates a new KeyList starting at |data|, total size is + // |range_size| (in bytes) + void create(uint8_t *data, size_t range_size) { + m_data = data; + m_range_size = range_size; + } + + // Opens an existing KeyList starting at |data| + void open(uint8_t *data, size_t range_size, size_t node_count) { + m_data = data; + m_range_size = range_size; + } + + // Calculates the required size for this range + size_t get_required_range_size(size_t node_count) const { + return (node_count * m_key_size); + } + + // Returns the actual key size including overhead + size_t get_full_key_size(const ham_key_t *key = 0) const { + return (m_key_size); + } + + // Copies a key into |dest| + void get_key(Context *context, int slot, ByteArray *arena, ham_key_t *dest, + bool deep_copy = true) const { + dest->size = (uint16_t)m_key_size; + if (likely(deep_copy == false)) { + dest->data = &m_data[slot * m_key_size]; + return; + } + + // allocate memory (if required) + if (!(dest->flags & HAM_KEY_USER_ALLOC)) { + arena->resize(dest->size); + dest->data = arena->get_ptr(); + } + + memcpy(dest->data, &m_data[slot * m_key_size], m_key_size); + } + + // Returns the threshold when switching from binary search to + // linear search + size_t get_linear_search_threshold() const { + if (m_key_size > 32) + return (-1); // disable linear search for large keys + return (128 / m_key_size); + } + + // Performs a linear search in a given range between |start| and + // |start + length| + template<typename Cmp> + int linear_search(size_t start, size_t length, const ham_key_t *key, + Cmp &comparator, int *pcmp) { + uint8_t *begin = &m_data[start * m_key_size]; + uint8_t *end = &m_data[(start + length) * m_key_size]; + uint8_t *current = begin; + + int c = start; + + while (current < end) { + /* compare it against the key */ + int cmp = comparator(key->data, key->size, current, m_key_size); + + /* found it, or moved past the key? */ + if (cmp <= 0) { + if (cmp < 0) { + if (c == 0) + *pcmp = -1; // key is < #m_data[0] + else + *pcmp = +1; // key is > #m_data[c - 1]! + return (c - 1); + } + *pcmp = 0; + return (c); + } + + current += m_key_size; + c++; + } + + /* the new key is > the last key in the page */ + *pcmp = 1; + return (start + length - 1); + } + + // Iterates all keys, calls the |visitor| on each + void scan(Context *context, ScanVisitor *visitor, uint32_t start, + size_t length) { + (*visitor)(&m_data[start * m_key_size], length); + } + + // Erases a whole slot by shifting all larger keys to the "left" + void erase(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count - 1) + memmove(&m_data[slot * m_key_size], &m_data[(slot + 1) * m_key_size], + m_key_size * (node_count - slot - 1)); + } + + // Inserts a key + template<typename Cmp> + PBtreeNode::InsertResult insert(Context *context, size_t node_count, + const ham_key_t *key, uint32_t flags, Cmp &comparator, + int slot) { + if (node_count > (size_t)slot) + memmove(&m_data[(slot + 1) * m_key_size], &m_data[slot * m_key_size], + m_key_size * (node_count - slot)); + set_key_data(slot, key->data, key->size); + return (PBtreeNode::InsertResult(0, slot)); + } + + // Returns true if the |key| no longer fits into the node + bool requires_split(size_t node_count, const ham_key_t *key) const { + return ((node_count + 1) * m_key_size >= m_range_size); + } + + // Copies |count| key from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, BinaryKeyList &dest, + size_t other_count, int dstart) { + memcpy(&dest.m_data[dstart * m_key_size], &m_data[sstart * m_key_size], + m_key_size * (node_count - sstart)); + } + + // Change the capacity; for PAX layouts this just means copying the + // data from one place to the other + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + memmove(new_data_ptr, m_data, node_count * m_key_size); + m_data = new_data_ptr; + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseKeyList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->keylist_unused, + m_range_size - (node_count * m_key_size)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) const { + for (size_t i = 0; i < m_key_size; i++) + out << (char)m_data[slot * m_key_size + i]; + } + + // Returns the key size + size_t get_key_size(int slot) const { + return (m_key_size); + } + + // Returns the pointer to a key's data + uint8_t *get_key_data(int slot) { + return (&m_data[slot * m_key_size]); + } + + // Has support for SIMD style search? + bool has_simd_support() const { + return (false); + } + + // Returns the pointer to the key's inline data - for SIMD calculations + // Not implemented by this KeyList + uint8_t *get_simd_data() { + return (0); + } + + private: + // Returns the pointer to a key's data (const flavour) + uint8_t *get_key_data(int slot) const { + return (&m_data[slot * m_key_size]); + } + + // Overwrites a key's data. The |size| of the new data HAS + // to be identical to the "official" key size + void set_key_data(int slot, const void *ptr, size_t size) { + ham_assert(size == get_key_size(slot)); + memcpy(&m_data[slot * m_key_size], ptr, size); + } + + // The size of a single key + size_t m_key_size; + + // Pointer to the actual key data + uint8_t *m_data; +}; + +} // namespace PaxLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_KEYS_BINARY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_pod.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_pod.h new file mode 100644 index 0000000000..1a0582da69 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_pod.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Fixed length KeyList for built-in data types ("POD types") + * + * This is the fastest KeyList available. It stores POD data sequentially + * in an array, i.e. PodKeyList<uint32_t> is simply a plain + * C array of type uint32_t[]. Each key has zero overhead. + * + * This KeyList cannot be resized. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_KEYS_POD_H +#define HAM_BTREE_KEYS_POD_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3btree/btree_node.h" +#include "3btree/btree_keys_base.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The template classes in this file are wrapped in a separate namespace +// to avoid naming clashes with btree_impl_default.h +// +namespace PaxLayout { + +// +// The PodKeyList provides simplified access to a list of keys where each +// key is of type T (i.e. uint32_t). +// +template<typename T> +class PodKeyList : public BaseKeyList +{ + public: + enum { + // A flag whether this KeyList has sequential data + kHasSequentialData = 1, + + // A flag whether this KeyList supports the scan() call + kSupportsBlockScans = 1, + + // This KeyList uses a custom SIMD implementation if possible, + // otherwise binary search in combination with linear search + kSearchImplementation = kBinaryLinear, + }; + + // Constructor + PodKeyList(LocalDatabase *db) + : m_data(0) { + } + + // Creates a new PodKeyList starting at |ptr|, total size is + // |range_size| (in bytes) + void create(uint8_t *data, size_t range_size) { + m_data = (T *)data; + m_range_size = range_size; + } + + // Opens an existing PodKeyList starting at |ptr| + void open(uint8_t *data, size_t range_size, size_t node_count) { + m_data = (T *)data; + m_range_size = range_size; + } + + // Returns the required size for the current set of keys + size_t get_required_range_size(size_t node_count) const { + return (node_count * sizeof(T)); + } + + // Returns the actual key size including overhead + size_t get_full_key_size(const ham_key_t *key = 0) const { + return (sizeof(T)); + } + + // Copies a key into |dest| + void get_key(Context *context, int slot, ByteArray *arena, ham_key_t *dest, + bool deep_copy = true) const { + dest->size = sizeof(T); + if (deep_copy == false) { + dest->data = &m_data[slot]; + return; + } + + // allocate memory (if required) + if (!(dest->flags & HAM_KEY_USER_ALLOC)) { + arena->resize(dest->size); + dest->data = arena->get_ptr(); + } + + memcpy(dest->data, &m_data[slot], sizeof(T)); + } + + // Returns the threshold when switching from binary search to + // linear search + size_t get_linear_search_threshold() const { + return (128 / sizeof(T)); + } + + // Performs a linear search in a given range between |start| and + // |start + length| + template<typename Cmp> + int linear_search(size_t start, size_t length, const ham_key_t *hkey, + Cmp &comparator, int *pcmp) { + T key = *(T *)hkey->data; + size_t c = start; + size_t end = start + length; + + #undef COMPARE + #define COMPARE(c) if (key <= m_data[c]) { \ + if (key < m_data[c]) { \ + if (c == 0) \ + *pcmp = -1; /* key < m_data[0] */ \ + else \ + *pcmp = +1; /* key > m_data[c - 1] */ \ + return ((c) - 1); \ + } \ + *pcmp = 0; \ + return (c); \ + } + + while (c + 8 < end) { + COMPARE(c) + COMPARE(c + 1) + COMPARE(c + 2) + COMPARE(c + 3) + COMPARE(c + 4) + COMPARE(c + 5) + COMPARE(c + 6) + COMPARE(c + 7) + c += 8; + } + + while (c < end) { + COMPARE(c) + c++; + } + + /* the new key is > the last key in the page */ + *pcmp = 1; + return (start + length - 1); + } + + // Iterates all keys, calls the |visitor| on each + void scan(Context *context, ScanVisitor *visitor, uint32_t start, + size_t length) { + (*visitor)(&m_data[start], length); + } + + // Erases a whole slot by shifting all larger keys to the "left" + void erase(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count - 1) + memmove(&m_data[slot], &m_data[slot + 1], + sizeof(T) * (node_count - slot - 1)); + } + + // Inserts a key + template<typename Cmp> + PBtreeNode::InsertResult insert(Context *context, size_t node_count, + const ham_key_t *key, uint32_t flags, Cmp &comparator, + int slot) { + if (node_count > (size_t)slot) + memmove(&m_data[slot + 1], &m_data[slot], + sizeof(T) * (node_count - slot)); + set_key_data(slot, key->data, key->size); + return (PBtreeNode::InsertResult(0, slot)); + } + + // Copies |count| key from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, PodKeyList<T> &dest, + size_t other_count, int dstart) { + memcpy(&dest.m_data[dstart], &m_data[sstart], + sizeof(T) * (node_count - sstart)); + } + + // Returns true if the |key| no longer fits into the node + bool requires_split(size_t node_count, const ham_key_t *key) const { + return ((node_count + 1) * sizeof(T) >= m_range_size); + } + + // Change the range size; just copy the data from one place to the other + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + memmove(new_data_ptr, m_data, node_count * sizeof(T)); + m_data = (T *)new_data_ptr; + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseKeyList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->keylist_unused, + m_range_size - (node_count * sizeof(T))); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) const { + out << m_data[slot]; + } + + // Returns the size of a key + size_t get_key_size(int slot) const { + return (sizeof(T)); + } + + // Returns a pointer to the key's data + uint8_t *get_key_data(int slot) { + return ((uint8_t *)&m_data[slot]); + } + + private: + // Returns a pointer to the key's data (const flavour) + uint8_t *get_key_data(int slot) const { + return ((uint8_t *)&m_data[slot]); + } + + // Overwrites an existing key; the |size| of the new data HAS to be + // identical with the key size specified when the database was created! + void set_key_data(int slot, const void *ptr, size_t size) { + ham_assert(size == sizeof(T)); + m_data[slot] = *(T *)ptr; + } + + // The actual array of T's + T *m_data; +}; + +} // namespace PaxLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_KEYS_POD_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_varlen.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_varlen.h new file mode 100644 index 0000000000..5f85676c56 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_keys_varlen.h @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Variable length KeyList + * + * Each key is stored in a "chunk", and the chunks are managed by an upfront + * index which contains offset and size of each chunk. The index also keeps + * track of deleted chunks. + * + * The actual chunk data contains the key's data (which can be a 64bit blob + * ID if the key is too big). + * + * If the key is too big (exceeds |m_extkey_threshold|) then it's offloaded + * to an external blob, and only the 64bit record id of this blob is stored + * in the node. These "extended keys" are cached; the cache's lifetime is + * coupled to the lifetime of the node. + * + * To avoid expensive memcpy-operations, erasing a key only affects this + * upfront index: the relevant slot is moved to a "freelist". This freelist + * contains the same meta information as the index table. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_KEYS_VARLEN_H +#define HAM_BTREE_KEYS_VARLEN_H + +#include "0root/root.h" + +#include <algorithm> +#include <iostream> +#include <vector> +#include <map> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "1base/scoped_ptr.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_node.h" +#include "3btree/btree_index.h" +#include "3btree/upfront_index.h" +#include "3btree/btree_keys_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +namespace DefLayout { + +// +// Variable length keys +// +// This KeyList uses an UpfrontIndex to manage the variable length data +// chunks. The UpfrontIndex knows the sizes of the chunks, and therefore +// the VariableLengthKeyList does *not* store additional size information. +// +// The format of a single key is: +// |Flags|Data...| +// where Flags are 8 bit. +// +// The key size (as specified by the user when inserting the key) therefore +// is UpfrontIndex::get_chunk_size() - 1. +// +class VariableLengthKeyList : public BaseKeyList +{ + // for caching external keys + typedef std::map<uint64_t, ByteArray> ExtKeyCache; + + public: + enum { + // A flag whether this KeyList has sequential data + kHasSequentialData = 0, + + // A flag whether this KeyList supports the scan() call + kSupportsBlockScans = 0, + + // This KeyList can reduce its capacity in order to release storage + kCanReduceCapacity = 1, + + // This KeyList uses binary search + kSearchImplementation = kBinarySearch, + }; + + // Constructor + VariableLengthKeyList(LocalDatabase *db) + : m_db(db), m_index(db), m_data(0) { + size_t page_size = db->lenv()->config().page_size_bytes; + if (Globals::ms_extended_threshold) + m_extkey_threshold = Globals::ms_extended_threshold; + else { + if (page_size == 1024) + m_extkey_threshold = 64; + else if (page_size <= 1024 * 8) + m_extkey_threshold = 128; + else { + // UpfrontIndex's chunk size has 8 bit (max 255), and reserve + // a few bytes for metadata (flags) + m_extkey_threshold = 250; + } + } + } + + // Creates a new KeyList starting at |ptr|, total size is + // |range_size| (in bytes) + void create(uint8_t *data, size_t range_size) { + m_data = data; + m_range_size = range_size; + m_index.create(m_data, range_size, range_size / get_full_key_size()); + } + + // Opens an existing KeyList + void open(uint8_t *data, size_t range_size, size_t node_count) { + m_data = data; + m_range_size = range_size; + m_index.open(m_data, range_size); + } + + // Calculates the required size for a range + size_t get_required_range_size(size_t node_count) const { + return (m_index.get_required_range_size(node_count)); + } + + // Returns the actual key size including overhead. This is an estimate + // since we don't know how large the keys will be + size_t get_full_key_size(const ham_key_t *key = 0) const { + if (!key) + return (24 + m_index.get_full_index_size() + 1); + // always make sure to have enough space for an extkey id + if (key->size < 8 || key->size > m_extkey_threshold) + return (sizeof(uint64_t) + m_index.get_full_index_size() + 1); + return (key->size + m_index.get_full_index_size() + 1); + } + + // Copies a key into |dest| + void get_key(Context *context, int slot, ByteArray *arena, ham_key_t *dest, + bool deep_copy = true) { + ham_key_t tmp; + uint32_t offset = m_index.get_chunk_offset(slot); + uint8_t *p = m_index.get_chunk_data_by_offset(offset); + + if (unlikely(*p & BtreeKey::kExtendedKey)) { + memset(&tmp, 0, sizeof(tmp)); + get_extended_key(context, get_extended_blob_id(slot), &tmp); + } + else { + tmp.size = get_key_size(slot); + tmp.data = p + 1; + } + + dest->size = tmp.size; + + if (likely(deep_copy == false)) { + dest->data = tmp.data; + return; + } + + // allocate memory (if required) + if (!(dest->flags & HAM_KEY_USER_ALLOC)) { + arena->resize(tmp.size); + dest->data = arena->get_ptr(); + } + memcpy(dest->data, tmp.data, tmp.size); + } + + // Iterates all keys, calls the |visitor| on each. Not supported by + // this KeyList implementation. For variable length keys, the caller + // must iterate over all keys. The |scan()| interface is only implemented + // for PAX style layouts. + void scan(Context *context, ScanVisitor *visitor, size_t node_count, + uint32_t start) { + ham_assert(!"shouldn't be here"); + throw Exception(HAM_INTERNAL_ERROR); + } + + // Erases a key's payload. Does NOT remove the chunk from the UpfrontIndex + // (see |erase()|). + void erase_extended_key(Context *context, int slot) { + uint8_t flags = get_key_flags(slot); + if (flags & BtreeKey::kExtendedKey) { + // delete the extended key from the cache + erase_extended_key(context, get_extended_blob_id(slot)); + // and transform into a key which is non-extended and occupies + // the same space as before, when it was extended + set_key_flags(slot, flags & (~BtreeKey::kExtendedKey)); + set_key_size(slot, sizeof(uint64_t)); + } + } + + // Erases a key, including extended blobs + void erase(Context *context, size_t node_count, int slot) { + erase_extended_key(context, slot); + m_index.erase(node_count, slot); + } + + // Inserts the |key| at the position identified by |slot|. + // This method cannot fail; there MUST be sufficient free space in the + // node (otherwise the caller would have split the node). + template<typename Cmp> + PBtreeNode::InsertResult insert(Context *context, size_t node_count, + const ham_key_t *key, uint32_t flags, + Cmp &comparator, int slot) { + m_index.insert(node_count, slot); + + // now there's one additional slot + node_count++; + + uint32_t key_flags = 0; + + // When inserting the data: always add 1 byte for key flags + if (key->size <= m_extkey_threshold + && m_index.can_allocate_space(node_count, key->size + 1)) { + uint32_t offset = m_index.allocate_space(node_count, slot, + key->size + 1); + uint8_t *p = m_index.get_chunk_data_by_offset(offset); + *p = key_flags; + memcpy(p + 1, key->data, key->size); // and data + } + else { + uint64_t blob_id = add_extended_key(context, key); + m_index.allocate_space(node_count, slot, 8 + 1); + set_extended_blob_id(slot, blob_id); + set_key_flags(slot, key_flags | BtreeKey::kExtendedKey); + } + + return (PBtreeNode::InsertResult(0, slot)); + } + + // Returns true if the |key| no longer fits into the node and a split + // is required. Makes sure that there is ALWAYS enough headroom + // for an extended key! + // + // If there's no key specified then always assume the worst case and + // pretend that the key has the maximum length + bool requires_split(size_t node_count, const ham_key_t *key) { + size_t required; + if (key) { + required = key->size + 1; + // add 1 byte for flags + if (key->size > m_extkey_threshold || key->size < 8 + 1) + required = 8 + 1; + } + else + required = m_extkey_threshold + 1; + return (m_index.requires_split(node_count, required)); + } + + // Copies |count| key from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, + VariableLengthKeyList &dest, size_t other_node_count, + int dstart) { + size_t to_copy = node_count - sstart; + ham_assert(to_copy > 0); + + // make sure that the other node has sufficient capacity in its + // UpfrontIndex + dest.m_index.change_range_size(other_node_count, 0, 0, + m_index.get_capacity()); + + for (size_t i = 0; i < to_copy; i++) { + size_t size = get_key_size(sstart + i); + + uint8_t *p = m_index.get_chunk_data_by_offset( + m_index.get_chunk_offset(sstart + i)); + uint8_t flags = *p; + uint8_t *data = p + 1; + + dest.m_index.insert(other_node_count + i, dstart + i); + // Add 1 byte for key flags + uint32_t offset = dest.m_index.allocate_space(other_node_count + i + 1, + dstart + i, size + 1); + p = dest.m_index.get_chunk_data_by_offset(offset); + *p = flags; // sets flags + memcpy(p + 1, data, size); // and data + } + + // A lot of keys will be invalidated after copying, therefore make + // sure that the next_offset is recalculated when it's required + m_index.invalidate_next_offset(); + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + void check_integrity(Context *context, size_t node_count) const { + ByteArray arena; + + // verify that the offsets and sizes are not overlapping + m_index.check_integrity(node_count); + + // make sure that extkeys are handled correctly + for (size_t i = 0; i < node_count; i++) { + if (get_key_size(i) > m_extkey_threshold + && !(get_key_flags(i) & BtreeKey::kExtendedKey)) { + ham_log(("key size %d, but key is not extended", get_key_size(i))); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + + if (get_key_flags(i) & BtreeKey::kExtendedKey) { + uint64_t blobid = get_extended_blob_id(i); + if (!blobid) { + ham_log(("integrity check failed: item %u " + "is extended, but has no blob", i)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + + // make sure that the extended blob can be loaded + ham_record_t record = {0}; + m_db->lenv()->blob_manager()->read(context, blobid, + &record, 0, &arena); + + // compare it to the cached key (if there is one) + if (m_extkey_cache) { + ExtKeyCache::iterator it = m_extkey_cache->find(blobid); + if (it != m_extkey_cache->end()) { + if (record.size != it->second.get_size()) { + ham_log(("Cached extended key differs from real key")); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + if (memcmp(record.data, it->second.get_ptr(), record.size)) { + ham_log(("Cached extended key differs from real key")); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + } + } + } + } + + // Rearranges the list + void vacuumize(size_t node_count, bool force) { + if (force) + m_index.increase_vacuumize_counter(100); + m_index.maybe_vacuumize(node_count); + } + + // Change the range size; the capacity will be adjusted, the data is + // copied as necessary + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + // no capacity given? then try to find a good default one + if (capacity_hint == 0) { + capacity_hint = (new_range_size - m_index.get_next_offset(node_count) + - get_full_key_size()) / m_index.get_full_index_size(); + if (capacity_hint <= node_count) + capacity_hint = node_count + 1; + } + + // if there's not enough space for the new capacity then try to reduce + // the capacity + if (m_index.get_next_offset(node_count) + get_full_key_size(0) + + capacity_hint * m_index.get_full_index_size() + + UpfrontIndex::kPayloadOffset + > new_range_size) + capacity_hint = node_count + 1; + + m_index.change_range_size(node_count, new_data_ptr, new_range_size, + capacity_hint); + m_data = new_data_ptr; + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseKeyList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->keylist_index, + (uint32_t)(m_index.get_capacity() + * m_index.get_full_index_size())); + BtreeStatistics::update_min_max_avg(&metrics->keylist_unused, + m_range_size + - (uint32_t)m_index.get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) { + ham_key_t tmp = {0}; + if (get_key_flags(slot) & BtreeKey::kExtendedKey) { + get_extended_key(context, get_extended_blob_id(slot), &tmp); + } + else { + tmp.size = get_key_size(slot); + tmp.data = get_key_data(slot); + } + out << (const char *)tmp.data; + } + + // Returns the pointer to a key's inline data (const flavour) + uint8_t *get_key_data(int slot) const { + uint32_t offset = m_index.get_chunk_offset(slot); + return (m_index.get_chunk_data_by_offset(offset) + 1); + } + + // Returns the size of a key + size_t get_key_size(int slot) const { + return (m_index.get_chunk_size(slot) - 1); + } + + private: + // Returns the flags of a key. Flags are defined in btree_flags.h + uint8_t get_key_flags(int slot) const { + uint32_t offset = m_index.get_chunk_offset(slot); + return (*m_index.get_chunk_data_by_offset(offset)); + } + + // Sets the flags of a key. Flags are defined in btree_flags.h + void set_key_flags(int slot, uint8_t flags) { + uint32_t offset = m_index.get_chunk_offset(slot); + *m_index.get_chunk_data_by_offset(offset) = flags; + } + + // Overwrites the (inline) data of the key + void set_key_data(int slot, const void *ptr, size_t size) { + ham_assert(m_index.get_chunk_size(slot) >= size); + set_key_size(slot, (uint16_t)size); + memcpy(get_key_data(slot), ptr, size); + } + + // Sets the size of a key + void set_key_size(int slot, size_t size) { + ham_assert(size + 1 <= m_index.get_chunk_size(slot)); + m_index.set_chunk_size(slot, size + 1); + } + + // Returns the record address of an extended key overflow area + uint64_t get_extended_blob_id(int slot) const { + return (*(uint64_t *)get_key_data(slot)); + } + + // Sets the record address of an extended key overflow area + void set_extended_blob_id(int slot, uint64_t blobid) { + *(uint64_t *)get_key_data(slot) = blobid; + } + + // Erases an extended key from disk and from the cache + void erase_extended_key(Context *context, uint64_t blobid) { + m_db->lenv()->blob_manager()->erase(context, blobid); + if (m_extkey_cache) { + ExtKeyCache::iterator it = m_extkey_cache->find(blobid); + if (it != m_extkey_cache->end()) + m_extkey_cache->erase(it); + } + } + + // Retrieves the extended key at |blobid| and stores it in |key|; will + // use the cache. + void get_extended_key(Context *context, uint64_t blob_id, ham_key_t *key) { + if (!m_extkey_cache) + m_extkey_cache.reset(new ExtKeyCache()); + else { + ExtKeyCache::iterator it = m_extkey_cache->find(blob_id); + if (it != m_extkey_cache->end()) { + key->size = it->second.get_size(); + key->data = it->second.get_ptr(); + return; + } + } + + ByteArray arena; + ham_record_t record = {0}; + m_db->lenv()->blob_manager()->read(context, blob_id, &record, + HAM_FORCE_DEEP_COPY, &arena); + (*m_extkey_cache)[blob_id] = arena; + arena.disown(); + key->data = record.data; + key->size = record.size; + } + + // Allocates an extended key and stores it in the cache + uint64_t add_extended_key(Context *context, const ham_key_t *key) { + if (!m_extkey_cache) + m_extkey_cache.reset(new ExtKeyCache()); + + ham_record_t rec = {0}; + rec.data = key->data; + rec.size = key->size; + + uint64_t blob_id = m_db->lenv()->blob_manager()->allocate( + context, &rec, 0); + ham_assert(blob_id != 0); + ham_assert(m_extkey_cache->find(blob_id) == m_extkey_cache->end()); + + ByteArray arena; + arena.resize(key->size); + memcpy(arena.get_ptr(), key->data, key->size); + (*m_extkey_cache)[blob_id] = arena; + arena.disown(); + + // increment counter (for statistics) + Globals::ms_extended_keys++; + + return (blob_id); + } + + // The database + LocalDatabase *m_db; + + // The index for managing the variable-length chunks + UpfrontIndex m_index; + + // Pointer to the data of the node + uint8_t *m_data; + + // Cache for extended keys + ScopedPtr<ExtKeyCache> m_extkey_cache; + + // Threshold for extended keys; if key size is > threshold then the + // key is moved to a blob + size_t m_extkey_threshold; +}; + +} // namespace DefLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_KEYS_VARLEN_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node.h new file mode 100644 index 0000000000..854e68e1a5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_NODE_H +#define HAM_BTREE_NODE_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "2page/page.h" +#include "3btree/btree_flags.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class PBtreeKeyDefault; + +#include "1base/packstart.h" + +/* + * A BtreeNode structure spans the persistent part of a Page + * + * This structure is directly written to/read from the file. + */ +HAM_PACK_0 struct HAM_PACK_1 PBtreeNode +{ + public: + // Result of the insert() operation + struct InsertResult { + InsertResult(ham_status_t _status = 0, int _slot = 0) + : status(_status), slot(_slot) { + } + + // hamsterdb status code + ham_status_t status; + + // the slot of the new (or existing) key + int slot; + }; + + enum { + // insert key at the beginning of the page + kInsertPrepend = 1, + + // append key to the end of the page + kInsertAppend = 2, + }; + + enum { + // node is a leaf + kLeafNode = 1 + }; + + // Returns a PBtreeNode from a Page + static PBtreeNode *from_page(Page *page) { + return ((PBtreeNode *)page->get_payload()); + } + + // Returns the offset (in bytes) of the member |m_data| + static uint32_t get_entry_offset() { + return (sizeof(PBtreeNode) - 1); + } + + // Returns the flags of the btree node (|kLeafNode|) + uint32_t get_flags() const { + return (m_flags); + } + + // Sets the flags of the btree node (|kLeafNode|) + void set_flags(uint32_t flags) { + m_flags = flags; + } + + // Returns the number of entries in a BtreeNode + uint32_t get_count() const { + return (m_count); + } + + // Sets the number of entries in a BtreeNode + void set_count(uint32_t count) { + m_count = count; + } + + // Returns the address of the left sibling of this node + uint64_t get_left() const { + return (m_left); + } + + // Sets the address of the left sibling of this node + void set_left(uint64_t left) { + m_left = left; + } + + // Returns the address of the right sibling of this node + uint64_t get_right() const { + return (m_right); + } + + // Sets the address of the right sibling of this node + void set_right(uint64_t right) { + m_right = right; + } + + // Returns the ptr_down of this node + uint64_t get_ptr_down() const { + return (m_ptr_down); + } + + // Returns true if this btree node is a leaf node + bool is_leaf() const { + return (m_flags & kLeafNode); + } + + // Sets the ptr_down of this node + void set_ptr_down(uint64_t ptr_down) { + m_ptr_down = ptr_down; + } + + // Returns a pointer to the key data + uint8_t *get_data() { + return (&m_data[0]); + } + + const uint8_t *get_data() const { + return (&m_data[0]); + } + + private: + // flags of this node + uint32_t m_flags; + + // number of used entries in the node + uint32_t m_count; + + // address of left sibling + uint64_t m_left; + + // address of right sibling + uint64_t m_right; + + // address of child node whose items are smaller than all items + // in this node + uint64_t m_ptr_down; + + // the entries of this node + uint8_t m_data[1]; + +} HAM_PACK_2; + +#include "1base/packstop.h" + +} // namespace hamsterdb + +#endif /* HAM_BTREE_NODE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node_proxy.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node_proxy.h new file mode 100644 index 0000000000..110bd05f08 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_node_proxy.h @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_NODE_PROXY_H +#define HAM_BTREE_NODE_PROXY_H + +#include "0root/root.h" + +#include <set> +#include <string.h> +#include <iostream> +#include <sstream> +#include <fstream> + +// Always verify that a file of level N does not include headers > N! +#include "1base/abi.h" +#include "1base/dynamic_array.h" +#include "1base/error.h" +#include "2page/page.h" +#include "3btree/btree_node.h" +#include "3blob_manager/blob_manager.h" +#include "4env/env_local.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +struct ScanVisitor; + +// +// A BtreeNodeProxy wraps a PBtreeNode structure and defines the actual +// format of the btree payload. +// +// The BtreeNodeProxy class provides access to the actual Btree nodes. The +// layout of those nodes depends heavily on the database configuration, +// and is implemented by template classes (btree_impl_default.h, +// btree_impl_pax.h.). +// +class BtreeNodeProxy +{ + public: + // Constructor + BtreeNodeProxy(Page *page) + : m_page(page) { + } + + // Destructor + virtual ~BtreeNodeProxy() { + } + + // Returns the flags of the btree node (|kLeafNode|) + uint32_t get_flags() const { + return (PBtreeNode::from_page(m_page)->get_flags()); + } + + // Sets the flags of the btree node (|kLeafNode|) + void set_flags(uint32_t flags) { + PBtreeNode::from_page(m_page)->set_flags(flags); + } + + // Returns the number of entries in the BtreeNode + size_t get_count() const { + return (PBtreeNode::from_page(m_page)->get_count()); + } + + // Sets the number of entries in the BtreeNode + void set_count(size_t count) { + PBtreeNode::from_page(m_page)->set_count((uint32_t)count); + } + + // Returns true if this btree node is a leaf node + bool is_leaf() const { + return (PBtreeNode::from_page(m_page)->is_leaf()); + } + + // Returns the address of the left sibling of this node + uint64_t get_left() const { + return (PBtreeNode::from_page(m_page)->get_left()); + } + + // Sets the address of the left sibling of this node + void set_left(uint64_t address) { + PBtreeNode::from_page(m_page)->set_left(address); + } + + // Returns the address of the right sibling of this node + uint64_t get_right() const { + return (PBtreeNode::from_page(m_page)->get_right()); + } + + // Sets the address of the right sibling of this node + void set_right(uint64_t address) { + PBtreeNode::from_page(m_page)->set_right(address); + } + + // Returns the ptr_down of this node + uint64_t get_ptr_down() const { + return (PBtreeNode::from_page(m_page)->get_ptr_down()); + } + + // Sets the ptr_down of this node + void set_ptr_down(uint64_t address) { + PBtreeNode::from_page(m_page)->set_ptr_down(address); + } + + // Returns the page pointer - const version + const Page *get_page() const { + return (m_page); + } + + // Returns the page pointer + Page *get_page() { + return (m_page); + } + + // Returns the estimated capacity of this node + virtual size_t estimate_capacity() const = 0; + + // Checks the integrity of the node. Throws an exception if it is + // not. Called by ham_db_check_integrity(). + virtual void check_integrity(Context *context) const = 0; + + // Iterates all keys, calls the |visitor| on each + virtual void scan(Context *context, ScanVisitor *visitor, + size_t start, bool distinct) = 0; + + // Compares the two keys. Returns 0 if both are equal, otherwise -1 (if + // |lhs| is greater) or +1 (if |rhs| is greater). + virtual int compare(const ham_key_t *lhs, const ham_key_t *rhs) const = 0; + + // Compares a public key and an internal key + virtual int compare(Context *context, const ham_key_t *lhs, int rhs) = 0; + + // Returns true if the public key (|lhs|) and an internal key (slot + // |rhs|) are equal + virtual bool equals(Context *context, const ham_key_t *lhs, int rhs) = 0; + + // Searches the node for the |key|, and returns the slot of this key. + // If |record_id| is not null then it will store the result of the last + // compare operation. + // If |pcmp| is not null then it will store the result of the last + // compare operation. + virtual int find_child(Context *context, ham_key_t *key, + uint64_t *record_id = 0, int *pcmp = 0) = 0; + + // Searches the node for the |key|, but will always return -1 if + // an exact match was not found + virtual int find_exact(Context *context, ham_key_t *key) = 0; + + // Returns the full key at the |slot|. Also resolves extended keys + // and respects HAM_KEY_USER_ALLOC in dest->flags. + virtual void get_key(Context *context, int slot, ByteArray *arena, + ham_key_t *dest) = 0; + + // Returns the number of records of a key at the given |slot|. This is + // either 1 or higher, but only if duplicate keys exist. + virtual int get_record_count(Context *context, int slot) = 0; + + // Returns the record size of a key or one of its duplicates. + virtual uint64_t get_record_size(Context *context, int slot, + int duplicate_index) = 0; + + // Returns the record id of the key at the given |slot| + // Only for internal nodes! + virtual uint64_t get_record_id(Context *context, int slot) const = 0; + + // Sets the record id of the key at the given |slot| + // Only for internal nodes! + virtual void set_record_id(Context *context, int slot, uint64_t id) = 0; + + // Returns the full record and stores it in |dest|. The record is identified + // by |slot| and |duplicate_index|. TINY and SMALL records are handled + // correctly, as well as HAM_DIRECT_ACCESS. + virtual void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index = 0) = 0; + + // High-level function to set a new record + // + // flags can be + // - HAM_OVERWRITE + // - HAM_DUPLICATE* + // + // a previously existing blob will be deleted if necessary + virtual void set_record(Context *context, int slot, ham_record_t *record, + int duplicate_index, uint32_t flags, + uint32_t *new_duplicate_index) = 0; + + // Removes the record (or the duplicate of it, if |duplicate_index| is > 0). + // If |all_duplicates| is set then all duplicates of this key are deleted. + // |has_duplicates_left| will return true if there are more duplicates left + // after the current one was deleted. + virtual void erase_record(Context *context, int slot, int duplicate_index, + bool all_duplicates, bool *has_duplicates_left) = 0; + + // High level function to remove an existing entry + virtual void erase(Context *context, int slot) = 0; + + // Erases all extended keys, overflow areas and records that are + // linked from this page; usually called when the Database is deleted + // or an In-Memory Database is freed + virtual void remove_all_entries(Context *context) = 0; + + // High level function to insert a new key. Only inserts the key. The + // actual record is then updated with |set_record|. + virtual PBtreeNode::InsertResult insert(Context *context, ham_key_t *key, + uint32_t flags) = 0; + + // Returns true if a node requires a split to insert a new |key| + virtual bool requires_split(Context *context, const ham_key_t *key = 0) = 0; + + // Returns true if a node requires a merge or a shift + virtual bool requires_merge() const = 0; + + // Splits a page and moves all elements at a position >= |pivot| + // to the |other| page. If the node is a leaf node then the pivot element + // is also copied, otherwise it is not because it will be propagated + // to the parent node instead (by the caller). + virtual void split(Context *context, BtreeNodeProxy *other, int pivot) = 0; + + // Merges all keys from the |other| node to this node + virtual void merge_from(Context *context, BtreeNodeProxy *other) = 0; + + // Fills the btree_metrics structure + virtual void fill_metrics(btree_metrics_t *metrics) = 0; + + // Prints the node to stdout. Only for testing and debugging! + virtual void print(Context *context, size_t node_count = 0) = 0; + + // Returns the class name. Only for testing! Uses the functions exported + // by abi.h, which are only available on assorted platforms. Other + // platforms will return empty strings. + virtual std::string test_get_classname() const = 0; + + protected: + Page *m_page; +}; + +// +// A comparator which uses a user-supplied callback function (installed +// with |ham_db_set_compare_func|) to compare two keys +// +struct CallbackCompare +{ + CallbackCompare(LocalDatabase *db) + : m_db(db) { + } + + int operator()(const void *lhs_data, uint32_t lhs_size, + const void *rhs_data, uint32_t rhs_size) const { + return (m_db->compare_func()((::ham_db_t *)m_db, (uint8_t *)lhs_data, + lhs_size, (uint8_t *)rhs_data, rhs_size)); + } + + LocalDatabase *m_db; +}; + +// +// A comparator for numeric keys. +// The actual type for the key is supplied with a template parameter. +// This has to be a POD type with support for operators < and >. +// +template<typename T> +struct NumericCompare +{ + NumericCompare(LocalDatabase *) { + } + + int operator()(const void *lhs_data, uint32_t lhs_size, + const void *rhs_data, uint32_t rhs_size) const { + ham_assert(lhs_size == rhs_size); + ham_assert(lhs_size == sizeof(T)); + T l = *(T *)lhs_data; + T r = *(T *)rhs_data; + return (l < r ? -1 : (l > r ? +1 : 0)); + } +}; + +// +// The default comparator for two keys, implemented with memcmp(3). +// Both keys have the same size! +// +struct FixedSizeCompare +{ + FixedSizeCompare(LocalDatabase *) { + } + + int operator()(const void *lhs_data, uint32_t lhs_size, + const void *rhs_data, uint32_t rhs_size) const { + ham_assert(lhs_size == rhs_size); + return (::memcmp(lhs_data, rhs_data, lhs_size)); + } +}; + +// +// The default comparator for two keys, implemented with memcmp(3). +// Both keys can have different sizes! shorter strings are treated as +// "greater" +// +struct VariableSizeCompare +{ + VariableSizeCompare(LocalDatabase *) { + } + + int operator()(const void *lhs_data, uint32_t lhs_size, + const void *rhs_data, uint32_t rhs_size) const { + if (lhs_size < rhs_size) { + int m = ::memcmp(lhs_data, rhs_data, lhs_size); + return (m == 0 ? -1 : m); + } + if (rhs_size < lhs_size) { + int m = ::memcmp(lhs_data, rhs_data, rhs_size); + return (m == 0 ? +1 : m); + } + return (::memcmp(lhs_data, rhs_data, lhs_size)); + } +}; + +// +// An implementation of the BtreeNodeProxy interface declared above. +// Its actual memory implementation of the btree keys/records is delegated +// to a template parameter |NodeImpl|, and the key comparisons are +// delegated to |Comparator|. +// +template<class NodeImpl, class Comparator> +class BtreeNodeProxyImpl : public BtreeNodeProxy +{ + typedef BtreeNodeProxyImpl<NodeImpl, Comparator> ClassType; + + public: + // Constructor + BtreeNodeProxyImpl(Page *page) + : BtreeNodeProxy(page), m_impl(page) { + } + + // Returns the estimated capacity of this node + virtual size_t estimate_capacity() const { + return (m_impl.estimate_capacity()); + } + + // Checks the integrity of the node + virtual void check_integrity(Context *context) const { + m_impl.check_integrity(context); + } + + // Iterates all keys, calls the |visitor| on each + virtual void scan(Context *context, ScanVisitor *visitor, + size_t start, bool distinct) { + m_impl.scan(context, visitor, start, distinct); + } + + // Compares two internal keys using the supplied comparator + virtual int compare(const ham_key_t *lhs, const ham_key_t *rhs) const { + Comparator cmp(m_page->get_db()); + return (cmp(lhs->data, lhs->size, rhs->data, rhs->size)); + } + + // Compares a public key and an internal key + virtual int compare(Context *context, const ham_key_t *lhs, int rhs) { + Comparator cmp(m_page->get_db()); + return (m_impl.compare(context, lhs, rhs, cmp)); + } + + // Returns true if the public key and an internal key are equal + virtual bool equals(Context *context, const ham_key_t *lhs, int rhs) { + return (0 == compare(context, lhs, rhs)); + } + + // Searches the node for the key and returns the slot of this key. + // If |pcmp| is not null then it will store the result of the last + // compare operation. + virtual int find_child(Context *context, ham_key_t *key, + uint64_t *precord_id = 0, int *pcmp = 0) { + int dummy; + if (get_count() == 0) { + if (pcmp) + *pcmp = 1; + if (precord_id) + *precord_id = get_ptr_down(); + return (-1); + } + Comparator cmp(m_page->get_db()); + return (m_impl.find_child(context, key, cmp, + precord_id ? precord_id : 0, + pcmp ? pcmp : &dummy)); + } + + // Searches the node for the |key|, but will always return -1 if + // an exact match was not found + virtual int find_exact(Context *context, ham_key_t *key) { + if (get_count() == 0) + return (-1); + Comparator cmp(m_page->get_db()); + return (m_impl.find_exact(context, key, cmp)); + } + + // Returns the full key at the |slot|. Also resolves extended keys + // and respects HAM_KEY_USER_ALLOC in dest->flags. + virtual void get_key(Context *context, int slot, ByteArray *arena, + ham_key_t *dest) { + m_impl.get_key(context, slot, arena, dest); + } + + // Returns the number of records of a key at the given |slot| + virtual int get_record_count(Context *context, int slot) { + ham_assert(slot < (int)get_count()); + return (m_impl.get_record_count(context, slot)); + } + + // Returns the full record and stores it in |dest|. The record is identified + // by |slot| and |duplicate_index|. TINY and SMALL records are handled + // correctly, as well as HAM_DIRECT_ACCESS. + virtual void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index = 0) { + ham_assert(slot < (int)get_count()); + m_impl.get_record(context, slot, arena, record, flags, duplicate_index); + } + + virtual void set_record(Context *context, int slot, ham_record_t *record, + int duplicate_index, uint32_t flags, + uint32_t *new_duplicate_index) { + m_impl.set_record(context, slot, record, duplicate_index, flags, + new_duplicate_index); + } + + // Returns the record size of a key or one of its duplicates + virtual uint64_t get_record_size(Context *context, int slot, + int duplicate_index) { + ham_assert(slot < (int)get_count()); + return (m_impl.get_record_size(context, slot, duplicate_index)); + } + + // Returns the record id of the key at the given |slot| + // Only for internal nodes! + virtual uint64_t get_record_id(Context *context, int slot) const { + ham_assert(slot < (int)get_count()); + return (m_impl.get_record_id(context, slot)); + } + + // Sets the record id of the key at the given |slot| + // Only for internal nodes! + virtual void set_record_id(Context *context, int slot, uint64_t id) { + return (m_impl.set_record_id(context, slot, id)); + } + + // High level function to remove an existing entry. Will call + // |erase_extended_key| to clean up (a potential) extended key, + // and |erase_record| on each record that is associated with the key. + virtual void erase(Context *context, int slot) { + ham_assert(slot < (int)get_count()); + m_impl.erase(context, slot); + set_count(get_count() - 1); + } + + // Removes the record (or the duplicate of it, if |duplicate_index| is > 0). + // If |all_duplicates| is set then all duplicates of this key are deleted. + // |has_duplicates_left| will return true if there are more duplicates left + // after the current one was deleted. + virtual void erase_record(Context *context, int slot, int duplicate_index, + bool all_duplicates, bool *has_duplicates_left) { + ham_assert(slot < (int)get_count()); + m_impl.erase_record(context, slot, duplicate_index, all_duplicates); + if (has_duplicates_left) + *has_duplicates_left = get_record_count(context, slot) > 0; + } + + // Erases all extended keys, overflow areas and records that are + // linked from this page; usually called when the Database is deleted + // or an In-Memory Database is closed + virtual void remove_all_entries(Context *context) { + size_t node_count = get_count(); + for (size_t i = 0; i < node_count; i++) { + m_impl.erase_extended_key(context, i); + + // If we're in the leaf page, delete the associated record. (Only + // leaf nodes have records; internal nodes have record IDs that + // reference other pages, and these pages must not be deleted.) + if (is_leaf()) + erase_record(context, i, 0, true, 0); + } + } + + // High level function to insert a new key. Only inserts the key. The + // actual record is then updated with |set_record|. + virtual PBtreeNode::InsertResult insert(Context *context, + ham_key_t *key, uint32_t flags) { + PBtreeNode::InsertResult result(0, 0); + if (m_impl.requires_split(context, key)) { + result.status = HAM_LIMITS_REACHED; + return (result); + } + + Comparator cmp(m_page->get_db()); + try { + result = m_impl.insert(context, key, flags, cmp); + } + catch (Exception &ex) { + result.status = ex.code; + } + + // split required? then reorganize the node, try again + if (result.status == HAM_LIMITS_REACHED) { + try { + if (m_impl.reorganize(context, key)) + result = m_impl.insert(context, key, flags, cmp); + } + catch (Exception &ex) { + result.status = ex.code; + } + } + + if (result.status == HAM_SUCCESS) + set_count(get_count() + 1); + + return (result); + } + + // Returns true if a node requires a split to insert |key| + virtual bool requires_split(Context *context, const ham_key_t *key = 0) { + return (m_impl.requires_split(context, key)); + } + + // Returns true if a node requires a merge or a shift + virtual bool requires_merge() const { + return (m_impl.requires_merge()); + } + + // Splits the node + virtual void split(Context *context, BtreeNodeProxy *other_node, + int pivot) { + ClassType *other = dynamic_cast<ClassType *>(other_node); + ham_assert(other != 0); + + m_impl.split(context, &other->m_impl, pivot); + + size_t node_count = get_count(); + set_count(pivot); + + if (is_leaf()) + other->set_count(node_count - pivot); + else + other->set_count(node_count - pivot - 1); + } + + // Merges all keys from the |other| node into this node + virtual void merge_from(Context *context, BtreeNodeProxy *other_node) { + ClassType *other = dynamic_cast<ClassType *>(other_node); + ham_assert(other != 0); + + m_impl.merge_from(context, &other->m_impl); + + set_count(get_count() + other->get_count()); + other->set_count(0); + } + + // Fills the btree_metrics structure + virtual void fill_metrics(btree_metrics_t *metrics) { + m_impl.fill_metrics(metrics, get_count()); + } + + // Prints the node to stdout (for debugging) + virtual void print(Context *context, size_t node_count = 0) { + std::cout << "page " << m_page->get_address() << ": " << get_count() + << " elements (leaf: " << (is_leaf() ? 1 : 0) << ", left: " + << get_left() << ", right: " << get_right() << ", ptr_down: " + << get_ptr_down() << ")" << std::endl; + if (!node_count) + node_count = get_count(); + for (size_t i = 0; i < node_count; i++) + m_impl.print(context, i); + } + + // Returns the class name. Only for testing! Uses the functions exported + // by abi.h, which are only available on assorted platforms. Other + // platforms will return empty strings. + virtual std::string test_get_classname() const { + return (get_classname(*this)); + } + + private: + NodeImpl m_impl; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_NODE_PROXY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_base.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_base.h new file mode 100644 index 0000000000..6128c8834d --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_base.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Base class for RecordLists + * + * @exception_safe: nothrow + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_RECORDS_BASE_H +#define HAM_BTREE_RECORDS_BASE_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct BaseRecordList +{ + BaseRecordList() + : m_range_size(0) { + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + void check_integrity(Context *context, size_t node_count) const { + } + + // Rearranges the list + void vacuumize(size_t node_count, bool force) const { + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BtreeStatistics::update_min_max_avg(&metrics->recordlist_ranges, + m_range_size); + } + + // The size of the range (in bytes) + size_t m_range_size; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_RECORDS_BASE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_default.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_default.h new file mode 100644 index 0000000000..6fcb6f1cb7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_default.h @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The DefaultRecordList provides simplified access to a list of records, + * where each record is either a 8-byte record identifier (specifying the + * address of a blob) or is stored inline, if the record's size is <= 8 bytes. + * + * Stores 1 byte of flags per record (see btree_flags.h). + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_RECORDS_DEFAULT_H +#define HAM_BTREE_RECORDS_DEFAULT_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_node.h" +#include "3btree/btree_records_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The template classes in this file are wrapped in a separate namespace +// to avoid naming clashes with btree_impl_default.h +// +namespace PaxLayout { + +class DefaultRecordList : public BaseRecordList +{ + public: + enum { + // A flag whether this RecordList has sequential data + kHasSequentialData = 1 + }; + + // Constructor + DefaultRecordList(LocalDatabase *db, PBtreeNode *node) + : m_db(db), m_flags(0), m_data(0) { + } + + // Sets the data pointer; required for initialization + void create(uint8_t *data, size_t range_size) { + size_t capacity = range_size / get_full_record_size(); + m_range_size = range_size; + + if (m_db->config().record_size == HAM_RECORD_SIZE_UNLIMITED) { + m_flags = data; + m_data = (uint64_t *)&data[capacity]; + } + else { + m_flags = 0; + m_data = (uint64_t *)data; + } + } + + // Opens an existing RecordList + void open(uint8_t *data, size_t range_size, size_t node_count) { + size_t capacity = range_size / get_full_record_size(); + m_range_size = range_size; + + if (m_db->config().record_size == HAM_RECORD_SIZE_UNLIMITED) { + m_flags = data; + m_data = (uint64_t *)&data[capacity]; + } + else { + m_flags = 0; + m_data = (uint64_t *)data; + } + } + + // Calculates the required size for a range + size_t get_required_range_size(size_t node_count) { + return (node_count * get_full_record_size()); + } + + // Returns the actual record size including overhead + size_t get_full_record_size() const { + return (sizeof(uint64_t) + + (m_db->config().record_size == HAM_RECORD_SIZE_UNLIMITED + ? 1 + : 0)); + } + + // Returns the record counter of a key + int get_record_count(Context *context, int slot) const { + if (unlikely(!is_record_inline(slot) && get_record_id(slot) == 0)) + return (0); + return (1); + } + + // Returns the record size + uint64_t get_record_size(Context *context, int slot, + int duplicate_index = 0) const { + if (is_record_inline(slot)) + return (get_inline_record_size(slot)); + + LocalEnvironment *env = m_db->lenv(); + return (env->blob_manager()->get_blob_size(context, get_record_id(slot))); + } + + // Returns the full record and stores it in |dest|; memory must be + // allocated by the caller + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index) const { + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + // the record is stored inline + if (is_record_inline(slot)) { + record->size = get_inline_record_size(slot); + if (record->size == 0) { + record->data = 0; + return; + } + if (flags & HAM_PARTIAL) { + ham_trace(("flag HAM_PARTIAL is not allowed if record is " + "stored inline")); + throw Exception(HAM_INV_PARAMETER); + } + if (direct_access) + record->data = (void *)&m_data[slot]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &m_data[slot], record->size); + } + return; + } + + // the record is stored as a blob + LocalEnvironment *env = m_db->lenv(); + env->blob_manager()->read(context, get_record_id(slot), record, + flags, arena); + } + + // Updates the record of a key + void set_record(Context *context, int slot, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index = 0) { + uint64_t ptr = get_record_id(slot); + LocalEnvironment *env = m_db->lenv(); + + // key does not yet exist + if (!ptr && !is_record_inline(slot)) { + // a new inline key is inserted + if (record->size <= sizeof(uint64_t)) { + set_record_data(slot, record->data, record->size); + } + // a new (non-inline) key is inserted + else { + ptr = env->blob_manager()->allocate(context, record, flags); + set_record_id(slot, ptr); + } + return; + } + + // an inline key exists + if (is_record_inline(slot)) { + // disable small/tiny/empty flags + set_record_flags(slot, get_record_flags(slot) + & ~(BtreeRecord::kBlobSizeSmall + | BtreeRecord::kBlobSizeTiny + | BtreeRecord::kBlobSizeEmpty)); + // ... and is overwritten with another inline key + if (record->size <= sizeof(uint64_t)) { + set_record_data(slot, record->data, record->size); + } + // ... or with a (non-inline) key + else { + ptr = env->blob_manager()->allocate(context, record, flags); + set_record_id(slot, ptr); + } + return; + } + + // a (non-inline) key exists + if (ptr) { + // ... and is overwritten by a inline key + if (record->size <= sizeof(uint64_t)) { + env->blob_manager()->erase(context, ptr); + set_record_data(slot, record->data, record->size); + } + // ... and is overwritten by a (non-inline) key + else { + ptr = env->blob_manager()->overwrite(context, ptr, record, flags); + set_record_id(slot, ptr); + } + return; + } + + ham_assert(!"shouldn't be here"); + throw Exception(HAM_INTERNAL_ERROR); + } + + // Erases the record + void erase_record(Context *context, int slot, int duplicate_index = 0, + bool all_duplicates = true) { + if (is_record_inline(slot)) { + remove_inline_record(slot); + return; + } + + // now erase the blob + m_db->lenv()->blob_manager()->erase(context, get_record_id(slot), 0); + set_record_id(slot, 0); + } + + // Erases a whole slot by shifting all larger records to the "left" + void erase(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count - 1) { + if (m_flags) + memmove(&m_flags[slot], &m_flags[slot + 1], node_count - slot - 1); + memmove(&m_data[slot], &m_data[slot + 1], + sizeof(uint64_t) * (node_count - slot - 1)); + } + } + + // Creates space for one additional record + void insert(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count) { + if (m_flags) + memmove(&m_flags[slot + 1], &m_flags[slot], node_count - slot); + memmove(&m_data[slot + 1], &m_data[slot], + sizeof(uint64_t) * (node_count - slot)); + } + if (m_flags) + m_flags[slot] = 0; + m_data[slot] = 0; + } + + // Copies |count| records from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, DefaultRecordList &dest, + size_t other_count, int dstart) { + if (m_flags) + memcpy(&dest.m_flags[dstart], &m_flags[sstart], (node_count - sstart)); + memcpy(&dest.m_data[dstart], &m_data[sstart], + sizeof(uint64_t) * (node_count - sstart)); + } + + // Sets the record id + void set_record_id(int slot, uint64_t ptr) { + m_data[slot] = ptr; + } + + // Returns the record id + uint64_t get_record_id(int slot, int duplicate_index = 0) const { + return (m_data[slot]); + } + + // Returns true if there's not enough space for another record + bool requires_split(size_t node_count) const { + return ((node_count + 1) * get_full_record_size() >= m_range_size); + } + + // Change the capacity; for PAX layouts this just means copying the + // data from one place to the other + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + size_t new_capacity = capacity_hint + ? capacity_hint + : new_range_size / get_full_record_size(); + // shift "to the right"? then first shift key data, otherwise + // the flags might overwrite the data + if (m_flags == 0) { + memmove(new_data_ptr, m_data, node_count * sizeof(uint64_t)); + } + else { + if (new_data_ptr > m_flags) { + memmove(&new_data_ptr[new_capacity], m_data, + node_count * sizeof(uint64_t)); + memmove(new_data_ptr, m_flags, node_count); + } + else { + memmove(new_data_ptr, m_flags, node_count); + memmove(&new_data_ptr[new_capacity], m_data, + node_count * sizeof(uint64_t)); + } + } + + if (m_db->config().record_size == HAM_RECORD_SIZE_UNLIMITED) { + m_flags = new_data_ptr; + m_data = (uint64_t *)&new_data_ptr[new_capacity]; + } + else { + m_flags = 0; + m_data = (uint64_t *)new_data_ptr; + } + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseRecordList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_unused, + m_range_size - get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) const { + out << "(" << get_record_size(context, slot) << " bytes)"; + } + + private: + // Sets record data + void set_record_data(int slot, const void *ptr, size_t size) { + uint8_t flags = get_record_flags(slot); + flags &= ~(BtreeRecord::kBlobSizeSmall + | BtreeRecord::kBlobSizeTiny + | BtreeRecord::kBlobSizeEmpty); + + if (size == 0) { + m_data[slot] = 0; + set_record_flags(slot, flags | BtreeRecord::kBlobSizeEmpty); + } + else if (size < 8) { + /* the highest byte of the record id is the size of the blob */ + char *p = (char *)&m_data[slot]; + p[sizeof(uint64_t) - 1] = size; + memcpy(&m_data[slot], ptr, size); + set_record_flags(slot, flags | BtreeRecord::kBlobSizeTiny); + } + else if (size == 8) { + memcpy(&m_data[slot], ptr, size); + set_record_flags(slot, flags | BtreeRecord::kBlobSizeSmall); + } + else { + ham_assert(!"shouldn't be here"); + set_record_flags(slot, flags); + } + } + + // Returns the record flags of a given |slot| + uint8_t get_record_flags(int slot, int duplicate_index = 0) + const { + return (m_flags ? m_flags[slot] : 0); + } + + // Sets the record flags of a given |slot| + void set_record_flags(int slot, uint8_t flags) { + ham_assert(m_flags != 0); + m_flags[slot] = flags; + } + + // Returns the size of an inline record + uint32_t get_inline_record_size(int slot) const { + uint8_t flags = get_record_flags(slot); + ham_assert(is_record_inline(slot)); + if (flags & BtreeRecord::kBlobSizeTiny) { + /* the highest byte of the record id is the size of the blob */ + char *p = (char *)&m_data[slot]; + return (p[sizeof(uint64_t) - 1]); + } + if (flags & BtreeRecord::kBlobSizeSmall) + return (sizeof(uint64_t)); + if (flags & BtreeRecord::kBlobSizeEmpty) + return (0); + ham_assert(!"shouldn't be here"); + return (0); + } + + // Returns true if the record is inline, false if the record is a blob + bool is_record_inline(int slot) const { + uint8_t flags = get_record_flags(slot); + return ((flags & BtreeRecord::kBlobSizeTiny) + || (flags & BtreeRecord::kBlobSizeSmall) + || (flags & BtreeRecord::kBlobSizeEmpty) != 0); + } + + // Removes an inline record; returns the updated record flags + void remove_inline_record(int slot) { + uint8_t flags = get_record_flags(slot); + m_data[slot] = 0; + set_record_flags(slot, + flags & ~(BtreeRecord::kBlobSizeSmall + | BtreeRecord::kBlobSizeTiny + | BtreeRecord::kBlobSizeEmpty)); + } + + // The parent database of this btree + LocalDatabase *m_db; + + // The record flags + uint8_t *m_flags; + + // The actual record data - an array of 64bit record IDs + uint64_t *m_data; +}; + +} // namespace PaxLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_RECORDS_DEFAULT_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_duplicate.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_duplicate.h new file mode 100644 index 0000000000..861f7a7640 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_duplicate.h @@ -0,0 +1,1557 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * RecordList implementations for duplicate records + * + * Duplicate records are stored inline till a certain threshold limit + * (m_duptable_threshold) is reached. In this case the duplicates are stored + * in a separate blob (the DuplicateTable), and the previously occupied storage + * in the node is reused for other records. + * + * Since records therefore have variable length, an UpfrontIndex is used + * (see btree_keys_varlen.h). + * + * This file has two RecordList implementations: + * + * - DuplicateRecordList: stores regular records as duplicates; records + * are stored as blobs if their size exceeds 8 bytes. Otherwise + * they are stored inline. + * + * - DuplicateInlineRecordList: stores small fixed length records as + * duplicates + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_RECORDS_DUPLICATE_H +#define HAM_BTREE_RECORDS_DUPLICATE_H + +#include "0root/root.h" + +#include <algorithm> +#include <iostream> +#include <vector> +#include <map> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/scoped_ptr.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_node.h" +#include "3btree/btree_index.h" +#include "3btree/upfront_index.h" +#include "3btree/btree_records_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +namespace DefLayout { + +// helper function which returns true if a record is inline +static bool is_record_inline(uint8_t flags) { + return (flags != 0); +} + +// +// A helper class for dealing with extended duplicate tables +// +// Byte [0..3] - count +// [4..7] - capacity +// [8.. [ - the record list +// if m_inline_records: +// each record has n bytes record-data +// else +// each record has 1 byte flags, n bytes record-data +// +class DuplicateTable +{ + public: + // Constructor; the flag |inline_records| indicates whether record + // flags should be stored for each record. |record_size| is the + // fixed length size of each record, or HAM_RECORD_SIZE_UNLIMITED + DuplicateTable(LocalDatabase *db, bool inline_records, size_t record_size) + : m_db(db), m_store_flags(!inline_records), m_record_size(record_size), + m_inline_records(inline_records), m_table_id(0) { + } + + // Allocates and fills the table; returns the new table id. + // Can allocate empty tables (required for testing purposes). + // The initial capacity of the table is twice the current + // |record_count|. + uint64_t create(Context *context, const uint8_t *data, + size_t record_count) { + ham_assert(m_table_id == 0); + + // This sets the initial capacity as described above + size_t capacity = record_count * 2; + m_table.resize(8 + capacity * get_record_width()); + if (likely(record_count > 0)) + m_table.overwrite(8, data, (m_inline_records + ? m_record_size * record_count + : 9 * record_count)); + + set_record_count(record_count); + set_record_capacity(record_count * 2); + + // Flush the table to disk, returns the blob-id of the table + return (flush_duplicate_table(context)); + } + + // Reads the table from disk + void open(Context *context, uint64_t table_id) { + ham_record_t record = {0}; + m_db->lenv()->blob_manager()->read(context, table_id, + &record, HAM_FORCE_DEEP_COPY, &m_table); + m_table_id = table_id; + } + + // Returns the number of duplicates in that table + int get_record_count() const { + ham_assert(m_table.get_size() > 4); + return ((int) *(uint32_t *)m_table.get_ptr()); + } + + // Returns the record size of a duplicate + uint64_t get_record_size(Context *context, int duplicate_index) { + ham_assert(duplicate_index < get_record_count()); + if (m_inline_records) + return (m_record_size); + ham_assert(m_store_flags == true); + + uint8_t *precord_flags; + uint8_t *p = get_record_data(duplicate_index, &precord_flags); + uint8_t flags = *precord_flags; + + if (flags & BtreeRecord::kBlobSizeTiny) + return (p[sizeof(uint64_t) - 1]); + if (flags & BtreeRecord::kBlobSizeSmall) + return (sizeof(uint64_t)); + if (flags & BtreeRecord::kBlobSizeEmpty) + return (0); + + uint64_t blob_id = *(uint64_t *)p; + return (m_db->lenv()->blob_manager()->get_blob_size(context, blob_id)); + } + + // Returns the full record and stores it in |record|. |flags| can + // be 0 or |HAM_DIRECT_ACCESS|, |HAM_PARTIAL|. These are the default + // flags of ham_db_find et al. + void get_record(Context *context, ByteArray *arena, ham_record_t *record, + uint32_t flags, int duplicate_index) { + ham_assert(duplicate_index < get_record_count()); + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + uint8_t *precord_flags; + uint8_t *p = get_record_data(duplicate_index, &precord_flags); + uint8_t record_flags = precord_flags ? *precord_flags : 0; + + if (m_inline_records) { + if (flags & HAM_PARTIAL) { + ham_trace(("flag HAM_PARTIAL is not allowed if record is " + "stored inline")); + throw Exception(HAM_INV_PARAMETER); + } + + record->size = m_record_size; + if (direct_access) + record->data = p; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, p, m_record_size); + } + return; + } + + ham_assert(m_store_flags == true); + + if (record_flags & BtreeRecord::kBlobSizeEmpty) { + record->data = 0; + record->size = 0; + return; + } + + if (record_flags & BtreeRecord::kBlobSizeTiny) { + record->size = p[sizeof(uint64_t) - 1]; + if (direct_access) + record->data = &p[0]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &p[0], record->size); + } + return; + } + + if (record_flags & BtreeRecord::kBlobSizeSmall) { + record->size = sizeof(uint64_t); + if (direct_access) + record->data = &p[0]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &p[0], record->size); + } + return; + } + + uint64_t blob_id = *(uint64_t *)p; + + // the record is stored as a blob + LocalEnvironment *env = m_db->lenv(); + env->blob_manager()->read(context, blob_id, record, flags, arena); + } + + // Updates the record of a key. Analog to the set_record() method + // of the NodeLayout class. Returns the new table id and the + // new duplicate index, if |new_duplicate_index| is not null. + uint64_t set_record(Context *context, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index) { + BlobManager *blob_manager = m_db->lenv()->blob_manager(); + + // the duplicate is overwritten + if (flags & HAM_OVERWRITE) { + uint8_t *record_flags = 0; + uint8_t *p = get_record_data(duplicate_index, &record_flags); + + // the record is stored inline w/ fixed length? + if (m_inline_records) { + ham_assert(record->size == m_record_size); + memcpy(p, record->data, record->size); + return (flush_duplicate_table(context)); + } + // the existing record is a blob + if (!is_record_inline(*record_flags)) { + uint64_t ptr = *(uint64_t *)p; + // overwrite the blob record + if (record->size > sizeof(uint64_t)) { + *(uint64_t *)p = blob_manager->overwrite(context, ptr, + record, flags); + return (flush_duplicate_table(context)); + } + // otherwise delete it and continue + blob_manager->erase(context, ptr, 0); + } + } + + // If the key is not overwritten but inserted or appended: create a + // "gap" in the table + else { + int record_count = get_record_count(); + + // check for overflow + if (unlikely(record_count == std::numeric_limits<int>::max())) { + ham_log(("Duplicate table overflow")); + throw Exception(HAM_LIMITS_REACHED); + } + + // adjust flags + if (flags & HAM_DUPLICATE_INSERT_BEFORE && duplicate_index == 0) + flags |= HAM_DUPLICATE_INSERT_FIRST; + else if (flags & HAM_DUPLICATE_INSERT_AFTER) { + if (duplicate_index == record_count) + flags |= HAM_DUPLICATE_INSERT_LAST; + else { + flags |= HAM_DUPLICATE_INSERT_BEFORE; + duplicate_index++; + } + } + + // resize the table, if necessary + if (unlikely(record_count == get_record_capacity())) + grow_duplicate_table(); + + // handle overwrites or inserts/appends + if (flags & HAM_DUPLICATE_INSERT_FIRST) { + if (record_count) { + uint8_t *ptr = get_raw_record_data(0); + memmove(ptr + get_record_width(), ptr, + record_count * get_record_width()); + } + duplicate_index = 0; + } + else if (flags & HAM_DUPLICATE_INSERT_BEFORE) { + uint8_t *ptr = get_raw_record_data(duplicate_index); + memmove(ptr + get_record_width(), ptr, + (record_count - duplicate_index) * get_record_width()); + } + else // HAM_DUPLICATE_INSERT_LAST + duplicate_index = record_count; + + set_record_count(record_count + 1); + } + + uint8_t *record_flags = 0; + uint8_t *p = get_record_data(duplicate_index, &record_flags); + + // store record inline? + if (m_inline_records) { + ham_assert(m_record_size == record->size); + if (m_record_size > 0) + memcpy(p, record->data, record->size); + } + else if (record->size == 0) { + memcpy(p, "\0\0\0\0\0\0\0\0", 8); + *record_flags = BtreeRecord::kBlobSizeEmpty; + } + else if (record->size < sizeof(uint64_t)) { + p[sizeof(uint64_t) - 1] = (uint8_t)record->size; + memcpy(&p[0], record->data, record->size); + *record_flags = BtreeRecord::kBlobSizeTiny; + } + else if (record->size == sizeof(uint64_t)) { + memcpy(&p[0], record->data, record->size); + *record_flags = BtreeRecord::kBlobSizeSmall; + } + else { + *record_flags = 0; + uint64_t blob_id = blob_manager->allocate(context, record, flags); + memcpy(p, &blob_id, sizeof(blob_id)); + } + + if (new_duplicate_index) + *new_duplicate_index = duplicate_index; + + // write the duplicate table to disk and return the table-id + return (flush_duplicate_table(context)); + } + + // Deletes a record from the table; also adjusts the count. If + // |all_duplicates| is true or if the last element of the table is + // deleted then the table itself will also be deleted. Returns 0 + // if this is the case, otherwise returns the table id. + uint64_t erase_record(Context *context, int duplicate_index, + bool all_duplicates) { + int record_count = get_record_count(); + + if (record_count == 1 && duplicate_index == 0) + all_duplicates = true; + + if (all_duplicates) { + if (m_store_flags && !m_inline_records) { + for (int i = 0; i < record_count; i++) { + uint8_t *record_flags; + uint8_t *p = get_record_data(i, &record_flags); + if (is_record_inline(*record_flags)) + continue; + if (*(uint64_t *)p != 0) { + m_db->lenv()->blob_manager()->erase(context, *(uint64_t *)p); + *(uint64_t *)p = 0; + } + } + } + if (m_table_id != 0) + m_db->lenv()->blob_manager()->erase(context, m_table_id); + set_record_count(0); + m_table_id = 0; + return (0); + } + + ham_assert(record_count > 0 && duplicate_index < record_count); + + uint8_t *record_flags; + uint8_t *lhs = get_record_data(duplicate_index, &record_flags); + if (record_flags != 0 && *record_flags == 0 && !m_inline_records) { + m_db->lenv()->blob_manager()->erase(context, *(uint64_t *)lhs); + *(uint64_t *)lhs = 0; + } + + if (duplicate_index < record_count - 1) { + lhs = get_raw_record_data(duplicate_index); + uint8_t *rhs = lhs + get_record_width(); + memmove(lhs, rhs, get_record_width() + * (record_count - duplicate_index - 1)); + } + + // adjust the counter + set_record_count(record_count - 1); + + // write the duplicate table to disk and return the table-id + return (flush_duplicate_table(context)); + } + + // Returns the maximum capacity of elements in a duplicate table + // This method could be private, but it's required by the unittests + int get_record_capacity() const { + ham_assert(m_table.get_size() >= 8); + return ((int) *(uint32_t *)((uint8_t *)m_table.get_ptr() + 4)); + } + + private: + // Doubles the capacity of the ByteArray which backs the table + void grow_duplicate_table() { + int capacity = get_record_capacity(); + if (capacity == 0) + capacity = 8; + m_table.resize(8 + (capacity * 2) * get_record_width()); + set_record_capacity(capacity * 2); + } + + // Writes the modified duplicate table to disk; returns the new + // table-id + uint64_t flush_duplicate_table(Context *context) { + ham_record_t record = {0}; + record.data = m_table.get_ptr(); + record.size = m_table.get_size(); + if (!m_table_id) + m_table_id = m_db->lenv()->blob_manager()->allocate( + context, &record, 0); + else + m_table_id = m_db->lenv()->blob_manager()->overwrite( + context, m_table_id, &record, 0); + return (m_table_id); + } + + // Returns the size of a record structure in the ByteArray + size_t get_record_width() const { + if (m_inline_records) + return (m_record_size); + ham_assert(m_store_flags == true); + return (sizeof(uint64_t) + 1); + } + + // Returns a pointer to the record data (including flags) + uint8_t *get_raw_record_data(int duplicate_index) { + if (m_inline_records) + return ((uint8_t *)m_table.get_ptr() + + 8 + + m_record_size * duplicate_index); + else + return ((uint8_t *)m_table.get_ptr() + + 8 + + 9 * duplicate_index); + } + + // Returns a pointer to the record data, and the flags + uint8_t *get_record_data(int duplicate_index, + uint8_t **pflags = 0) { + uint8_t *p = get_raw_record_data(duplicate_index); + if (m_store_flags) { + if (pflags) + *pflags = p++; + else + p++; + } + else if (pflags) + *pflags = 0; + return (p); + } + + // Sets the number of used elements in a duplicate table + void set_record_count(int record_count) { + *(uint32_t *)m_table.get_ptr() = (uint32_t)record_count; + } + + // Sets the maximum capacity of elements in a duplicate table + void set_record_capacity(int capacity) { + ham_assert(m_table.get_size() >= 8); + *(uint32_t *)((uint8_t *)m_table.get_ptr() + 4) = (uint32_t)capacity; + } + + // The database + LocalDatabase *m_db; + + // Whether to store flags per record or not (true unless records + // have constant length) + bool m_store_flags; + + // The constant length record size, or HAM_RECORD_SIZE_UNLIMITED + size_t m_record_size; + + // Stores the actual data of the table + ByteArray m_table; + + // True if records are inline + bool m_inline_records; + + // The blob id for persisting the table + uint64_t m_table_id; +}; + +// +// Common functions for duplicate record lists +// +class DuplicateRecordList : public BaseRecordList +{ + protected: + // for caching external duplicate tables + typedef std::map<uint64_t, DuplicateTable *> DuplicateTableCache; + + public: + enum { + // A flag whether this RecordList has sequential data + kHasSequentialData = 0 + }; + + // Constructor + DuplicateRecordList(LocalDatabase *db, PBtreeNode *node, + bool store_flags, size_t record_size) + : m_db(db), m_node(node), m_index(db), m_data(0), + m_store_flags(store_flags), m_record_size(record_size) { + size_t page_size = db->lenv()->config().page_size_bytes; + if (Globals::ms_duplicate_threshold) + m_duptable_threshold = Globals::ms_duplicate_threshold; + else { + if (page_size == 1024) + m_duptable_threshold = 8; + else if (page_size <= 1024 * 8) + m_duptable_threshold = 12; + else if (page_size <= 1024 * 16) + m_duptable_threshold = 20; + else if (page_size <= 1024 * 32) + m_duptable_threshold = 32; + else { + // 0x7f/127 is the maximum that we can store in the record + // counter (7 bits), but we won't exploit this fully + m_duptable_threshold = 64; + } + } + + // UpfrontIndex's chunk_size is just 1 byte (max 255); make sure that + // the duplicate list fits into a single chunk! + size_t rec_size = m_record_size; + if (rec_size == HAM_RECORD_SIZE_UNLIMITED) + rec_size = 9; + if (m_duptable_threshold * rec_size > 250) + m_duptable_threshold = 250 / rec_size; + } + + // Destructor - clears the cache + ~DuplicateRecordList() { + if (m_duptable_cache) { + for (DuplicateTableCache::iterator it = m_duptable_cache->begin(); + it != m_duptable_cache->end(); it++) + delete it->second; + } + } + + // Opens an existing RecordList + void open(uint8_t *ptr, size_t range_size, size_t node_count) { + m_data = ptr; + m_index.open(m_data, range_size); + m_range_size = range_size; + } + + // Returns a duplicate table; uses a cache to speed up access + DuplicateTable *get_duplicate_table(Context *context, uint64_t table_id) { + if (!m_duptable_cache) + m_duptable_cache.reset(new DuplicateTableCache()); + else { + DuplicateTableCache::iterator it = m_duptable_cache->find(table_id); + if (it != m_duptable_cache->end()) + return (it->second); + } + + DuplicateTable *dt = new DuplicateTable(m_db, !m_store_flags, + m_record_size); + dt->open(context, table_id); + (*m_duptable_cache)[table_id] = dt; + return (dt); + } + + // Updates the DupTableCache and changes the table id of a DuplicateTable. + // Called whenever a DuplicateTable's size increases, and the new blob-id + // differs from the old one. + void update_duplicate_table_id(DuplicateTable *dt, + uint64_t old_table_id, uint64_t new_table_id) { + m_duptable_cache->erase(old_table_id); + (*m_duptable_cache)[new_table_id] = dt; + } + + // Erases a slot. Only updates the UpfrontIndex; does NOT delete the + // record blobs! + void erase(Context *context, size_t node_count, int slot) { + m_index.erase(node_count, slot); + } + + // Inserts a slot for one additional record + void insert(Context *context, size_t node_count, int slot) { + m_index.insert(node_count, slot); + } + + // Copies |count| items from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, + DuplicateRecordList &dest, size_t other_node_count, + int dstart) { + // make sure that the other node has sufficient capacity in its + // UpfrontIndex + dest.m_index.change_range_size(other_node_count, 0, 0, + m_index.get_capacity()); + + uint32_t doffset; + for (size_t i = 0; i < node_count - sstart; i++) { + size_t size = m_index.get_chunk_size(sstart + i); + + dest.m_index.insert(other_node_count + i, dstart + i); + // destination offset + doffset = dest.m_index.allocate_space(other_node_count + i + 1, + dstart + i, size); + doffset = dest.m_index.get_absolute_offset(doffset); + // source offset + uint32_t soffset = m_index.get_chunk_offset(sstart + i); + soffset = m_index.get_absolute_offset(soffset); + // copy the data + memcpy(&dest.m_data[doffset], &m_data[soffset], size); + } + + // After copying, the caller will reduce the node count drastically. + // Therefore invalidate the cached next_offset. + m_index.invalidate_next_offset(); + } + + // Rearranges the list + void vacuumize(size_t node_count, bool force) { + if (force) + m_index.increase_vacuumize_counter(100); + m_index.maybe_vacuumize(node_count); + } + + protected: + // The database + LocalDatabase *m_db; + + // The current node + PBtreeNode *m_node; + + // The index which manages variable length chunks + UpfrontIndex m_index; + + // The actual data of the node + uint8_t *m_data; + + // Whether record flags are required + bool m_store_flags; + + // The constant record size, or HAM_RECORD_SIZE_UNLIMITED + size_t m_record_size; + + // The duplicate threshold + size_t m_duptable_threshold; + + // A cache for duplicate tables + ScopedPtr<DuplicateTableCache> m_duptable_cache; +}; + +// +// RecordList for records with fixed length, with duplicates. It uses +// an UpfrontIndex to manage the variable length chunks. +// +// If a key has duplicates, then all duplicates are stored sequentially. +// If that duplicate list exceeds a certain threshold then they are moved +// to a DuplicateTable, which is stored as a blob. +// +// Format for each slot: +// +// 1 byte meta data +// bit 1 - 7: duplicate counter, if kExtendedDuplicates == 0 +// bit 8: kExtendedDuplicates +// if kExtendedDuplicates == 0: +// <counter> * <length> bytes +// <length> byte data (always inline) +// if kExtendedDuplicates == 1: +// 8 byte: record id of the extended duplicate table +// +class DuplicateInlineRecordList : public DuplicateRecordList +{ + public: + // Constructor + DuplicateInlineRecordList(LocalDatabase *db, PBtreeNode *node) + : DuplicateRecordList(db, node, false, db->config().record_size), + m_record_size(db->config().record_size) { + } + + // Creates a new RecordList starting at |data| + void create(uint8_t *data, size_t range_size) { + m_data = data; + m_index.create(m_data, range_size, range_size / get_full_record_size()); + m_range_size = range_size; + } + + // Calculates the required size for a range with the specified |capacity| + size_t get_required_range_size(size_t node_count) const { + return (m_index.get_required_range_size(node_count)); + } + + // Returns the actual record size including overhead + size_t get_full_record_size() const { + return (1 + m_record_size + m_index.get_full_index_size()); + } + + // Returns the number of duplicates for a slot + int get_record_count(Context *context, int slot) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + if (m_data[offset] & BtreeRecord::kExtendedDuplicates) { + DuplicateTable *dt = get_duplicate_table(context, get_record_id(slot)); + return ((int)dt->get_record_count()); + } + + return (m_data[offset] & 0x7f); + } + + // Returns the size of a record; the size is always constant + uint64_t get_record_size(Context *context, int slot, + int duplicate_index = 0) const { + return (m_record_size); + } + + // Returns the full record and stores it in |dest| + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index) { + // forward to duplicate table? + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + DuplicateTable *dt = get_duplicate_table(context, get_record_id(slot)); + dt->get_record(context, arena, record, flags, duplicate_index); + return; + } + + if (flags & HAM_PARTIAL) { + ham_trace(("flag HAM_PARTIAL is not allowed if record is " + "stored inline")); + throw Exception(HAM_INV_PARAMETER); + } + + ham_assert(duplicate_index < (int)get_inline_record_count(slot)); + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + // the record is always stored inline + const uint8_t *ptr = get_record_data(slot, duplicate_index); + record->size = m_record_size; + if (direct_access) + record->data = (void *)ptr; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, ptr, m_record_size); + } + } + + // Adds or overwrites a record + void set_record(Context *context, int slot, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index = 0) { + uint32_t chunk_offset = m_index.get_absolute_chunk_offset(slot); + uint32_t current_size = m_index.get_chunk_size(slot); + + ham_assert(m_record_size == record->size); + + // if the slot was not yet allocated: allocate new space, initialize + // it and then overwrite the record + if (current_size == 0) { + duplicate_index = 0; + flags |= HAM_OVERWRITE; + chunk_offset = m_index.allocate_space(m_node->get_count(), slot, + 1 + m_record_size); + chunk_offset = m_index.get_absolute_offset(chunk_offset); + // clear the flags + m_data[chunk_offset] = 0; + + set_inline_record_count(slot, 1); + } + + // if there's no duplicate table, but we're not able to add another + // duplicate because of size constraints, then offload all + // existing duplicates to an external DuplicateTable + uint32_t record_count = get_inline_record_count(slot); + size_t required_size = 1 + (record_count + 1) * m_record_size; + + if (!(m_data[chunk_offset] & BtreeRecord::kExtendedDuplicates) + && !(flags & HAM_OVERWRITE)) { + bool force_duptable = record_count >= m_duptable_threshold; + if (!force_duptable + && !m_index.can_allocate_space(m_node->get_count(), + required_size)) + force_duptable = true; + + // update chunk_offset - it might have been modified if + // m_index.can_allocate_space triggered a vacuumize() operation + chunk_offset = m_index.get_absolute_chunk_offset(slot); + + // already too many duplicates, or the record does not fit? then + // allocate an overflow duplicate list and move all duplicates to + // this list + if (force_duptable) { + DuplicateTable *dt = new DuplicateTable(m_db, !m_store_flags, + m_record_size); + uint64_t table_id = dt->create(context, get_record_data(slot, 0), + record_count); + if (!m_duptable_cache) + m_duptable_cache.reset(new DuplicateTableCache()); + (*m_duptable_cache)[table_id] = dt; + + // write the id of the duplicate table + if (m_index.get_chunk_size(slot) < 8 + 1) { + // do not erase the slot because it occupies so little space + size_t node_count = m_node->get_count(); + // force a split in the caller if the duplicate table cannot + // be inserted + if (!m_index.can_allocate_space(node_count, 8 + 1)) + throw Exception(HAM_LIMITS_REACHED); + m_index.allocate_space(node_count, slot, 8 + 1); + chunk_offset = m_index.get_absolute_chunk_offset(slot); + } + + m_data[chunk_offset] |= BtreeRecord::kExtendedDuplicates; + set_record_id(slot, table_id); + set_inline_record_count(slot, 0); + + m_index.set_chunk_size(slot, 8 + 1); + m_index.increase_vacuumize_counter(m_index.get_chunk_size(slot) - 9); + m_index.invalidate_next_offset(); + + // fall through + } + } + + // forward to duplicate table? + if (unlikely(m_data[chunk_offset] & BtreeRecord::kExtendedDuplicates)) { + uint64_t table_id = get_record_id(slot); + DuplicateTable *dt = get_duplicate_table(context, table_id); + uint64_t new_table_id = dt->set_record(context, duplicate_index, record, + flags, new_duplicate_index); + if (new_table_id != table_id) { + update_duplicate_table_id(dt, table_id, new_table_id); + set_record_id(slot, new_table_id); + } + return; + } + + // the duplicate is overwritten + if (flags & HAM_OVERWRITE) { + // the record is always stored inline w/ fixed length + uint8_t *p = (uint8_t *)get_record_data(slot, duplicate_index); + memcpy(p, record->data, record->size); + return; + } + + // Allocate new space for the duplicate table, if required + if (current_size < required_size) { + uint8_t *oldp = &m_data[chunk_offset]; + uint32_t old_chunk_size = m_index.get_chunk_size(slot); + uint32_t old_chunk_offset = m_index.get_chunk_offset(slot); + uint32_t new_chunk_offset = m_index.allocate_space(m_node->get_count(), + slot, required_size); + chunk_offset = m_index.get_absolute_offset(new_chunk_offset); + if (current_size > 0 && old_chunk_offset != new_chunk_offset) { + memmove(&m_data[chunk_offset], oldp, current_size); + m_index.add_to_freelist(m_node->get_count(), old_chunk_offset, + old_chunk_size); + } + } + + // adjust flags + if (flags & HAM_DUPLICATE_INSERT_BEFORE && duplicate_index == 0) + flags |= HAM_DUPLICATE_INSERT_FIRST; + else if (flags & HAM_DUPLICATE_INSERT_AFTER) { + if (duplicate_index == (int)record_count) + flags |= HAM_DUPLICATE_INSERT_LAST; + else { + flags |= HAM_DUPLICATE_INSERT_BEFORE; + duplicate_index++; + } + } + + // handle overwrites or inserts/appends + if (flags & HAM_DUPLICATE_INSERT_FIRST) { + if (record_count > 0) { + uint8_t *ptr = get_record_data(slot, 0); + memmove(get_record_data(slot, 1), ptr, record_count * m_record_size); + } + duplicate_index = 0; + } + else if (flags & HAM_DUPLICATE_INSERT_BEFORE) { + memmove(get_record_data(slot, duplicate_index), + get_record_data(slot, duplicate_index + 1), + (record_count - duplicate_index) * m_record_size); + } + else // HAM_DUPLICATE_INSERT_LAST + duplicate_index = record_count; + + set_inline_record_count(slot, record_count + 1); + + // store the new record inline + if (m_record_size > 0) + memcpy(get_record_data(slot, duplicate_index), + record->data, record->size); + + if (new_duplicate_index) + *new_duplicate_index = duplicate_index; + } + + // Erases a record's blob (does not remove the slot!) + void erase_record(Context *context, int slot, int duplicate_index = 0, + bool all_duplicates = false) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + + // forward to external duplicate table? + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + uint64_t table_id = get_record_id(slot); + DuplicateTable *dt = get_duplicate_table(context, table_id); + uint64_t new_table_id = dt->erase_record(context, duplicate_index, + all_duplicates); + if (new_table_id == 0) { + m_duptable_cache->erase(table_id); + set_record_id(slot, 0); + m_data[offset] &= ~BtreeRecord::kExtendedDuplicates; + delete dt; + } + else if (new_table_id != table_id) { + update_duplicate_table_id(dt, table_id, new_table_id); + set_record_id(slot, new_table_id); + } + return; + } + + // there's only one record left which is erased? + size_t node_count = get_inline_record_count(slot); + if (node_count == 1 && duplicate_index == 0) + all_duplicates = true; + + // erase all duplicates? + if (all_duplicates) { + set_inline_record_count(slot, 0); + } + else { + if (duplicate_index < (int)node_count - 1) + memmove(get_record_data(duplicate_index), + get_record_data(duplicate_index + 1), + m_record_size * (node_count - duplicate_index - 1)); + set_inline_record_count(slot, node_count - 1); + } + } + + // Returns a 64bit record id from a record + uint64_t get_record_id(int slot, + int duplicate_index = 0) const { + return (*(uint64_t *)get_record_data(slot, duplicate_index)); + } + + // Sets a 64bit record id; used for internal nodes to store Page IDs + // or for leaf nodes to store DuplicateTable IDs + void set_record_id(int slot, uint64_t id) { + ham_assert(m_index.get_chunk_size(slot) >= sizeof(id)); + *(uint64_t *)get_record_data(slot, 0) = id; + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + void check_integrity(Context *context, size_t node_count, + bool quick = false) const { + for (size_t i = 0; i < node_count; i++) { + uint32_t offset = m_index.get_absolute_chunk_offset(i); + if (m_data[offset] & BtreeRecord::kExtendedDuplicates) { + ham_assert((m_data[offset] & 0x7f) == 0); + } + } + + m_index.check_integrity(node_count); + } + + // Change the capacity; the capacity will be reduced, growing is not + // implemented. Which means that the data area must be copied; the offsets + // do not have to be changed. + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + // no capacity given? then try to find a good default one + if (capacity_hint == 0) { + capacity_hint = (new_range_size - m_index.get_next_offset(node_count) + - get_full_record_size()) / m_index.get_full_index_size(); + if (capacity_hint <= node_count) + capacity_hint = node_count + 1; + } + + // if there's not enough space for the new capacity then try to reduce + // the capacity + if (m_index.get_next_offset(node_count) + get_full_record_size() + + capacity_hint * m_index.get_full_index_size() + + UpfrontIndex::kPayloadOffset + > new_range_size) + capacity_hint = node_count + 1; + + m_index.change_range_size(node_count, new_data_ptr, new_range_size, + capacity_hint); + m_data = new_data_ptr; + m_range_size = new_range_size; + } + + // Returns true if there's not enough space for another record + bool requires_split(size_t node_count) { + // if the record is extremely small then make sure there's some headroom; + // this is required for DuplicateTable ids which are 64bit numbers + size_t required = get_full_record_size(); + if (required < 10) + required = 10; + return (m_index.requires_split(node_count, required)); + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseRecordList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_index, + m_index.get_capacity() * m_index.get_full_index_size()); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_unused, + m_range_size - get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) { + out << "(" << get_record_count(context, slot) << " records)"; + } + + private: + // Returns the number of records that are stored inline + uint32_t get_inline_record_count(int slot) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (m_data[offset] & 0x7f); + } + + // Sets the number of records that are stored inline + void set_inline_record_count(int slot, size_t count) { + ham_assert(count <= 0x7f); + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + m_data[offset] &= BtreeRecord::kExtendedDuplicates; + m_data[offset] |= count; + } + + // Returns a pointer to the record data + uint8_t *get_record_data(int slot, int duplicate_index = 0) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (&m_data[offset + 1 + m_record_size * duplicate_index]); + } + + // Returns a pointer to the record data (const flavour) + const uint8_t *get_record_data(int slot, + int duplicate_index = 0) const { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (&m_data[offset + 1 + m_record_size * duplicate_index]); + } + + // The constant length record size + size_t m_record_size; +}; + +// +// RecordList for default records (8 bytes; either inline or a record id), +// with duplicates +// +// Format for each slot: +// +// 1 byte meta data +// bit 1 - 7: duplicate counter, if kExtendedDuplicates == 0 +// bit 8: kExtendedDuplicates +// if kExtendedDuplicates == 0: +// <counter> * 9 bytes +// 1 byte flags (RecordFlag::*) +// 8 byte data (either inline or record-id) +// if kExtendedDuplicates == 1: +// 8 byte: record id of the extended duplicate table +// +class DuplicateDefaultRecordList : public DuplicateRecordList +{ + public: + // Constructor + DuplicateDefaultRecordList(LocalDatabase *db, PBtreeNode *node) + : DuplicateRecordList(db, node, true, HAM_RECORD_SIZE_UNLIMITED) { + } + + // Creates a new RecordList starting at |data| + void create(uint8_t *data, size_t range_size) { + m_data = data; + m_index.create(m_data, range_size, range_size / get_full_record_size()); + } + + // Calculates the required size for a range with the specified |capacity| + size_t get_required_range_size(size_t node_count) const { + return (m_index.get_required_range_size(node_count)); + } + + // Returns the actual key record including overhead + size_t get_full_record_size() const { + return (1 + 1 + 8 + m_index.get_full_index_size()); + } + + // Returns the number of duplicates + int get_record_count(Context *context, int slot) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + DuplicateTable *dt = get_duplicate_table(context, get_record_id(slot)); + return ((int) dt->get_record_count()); + } + + return (m_data[offset] & 0x7f); + } + + // Returns the size of a record + uint64_t get_record_size(Context *context, int slot, + int duplicate_index = 0) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + DuplicateTable *dt = get_duplicate_table(context, get_record_id(slot)); + return (dt->get_record_size(context, duplicate_index)); + } + + uint8_t *p = &m_data[offset + 1 + 9 * duplicate_index]; + uint8_t flags = *(p++); + if (flags & BtreeRecord::kBlobSizeTiny) + return (p[sizeof(uint64_t) - 1]); + if (flags & BtreeRecord::kBlobSizeSmall) + return (sizeof(uint64_t)); + if (flags & BtreeRecord::kBlobSizeEmpty) + return (0); + + LocalEnvironment *env = m_db->lenv(); + return (env->blob_manager()->get_blob_size(context, *(uint64_t *)p)); + } + + // Returns the full record and stores it in |dest|; memory must be + // allocated by the caller + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, int duplicate_index) { + // forward to duplicate table? + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + DuplicateTable *dt = get_duplicate_table(context, get_record_id(slot)); + dt->get_record(context, arena, record, flags, duplicate_index); + return; + } + + ham_assert(duplicate_index < (int)get_inline_record_count(slot)); + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + uint8_t *p = &m_data[offset + 1 + 9 * duplicate_index]; + uint8_t record_flags = *(p++); + + if (record_flags && (flags & HAM_PARTIAL)) { + ham_trace(("flag HAM_PARTIAL is not allowed if record is " + "stored inline")); + throw Exception(HAM_INV_PARAMETER); + } + + if (record_flags & BtreeRecord::kBlobSizeEmpty) { + record->data = 0; + record->size = 0; + return; + } + + if (record_flags & BtreeRecord::kBlobSizeTiny) { + record->size = p[sizeof(uint64_t) - 1]; + if (direct_access) + record->data = &p[0]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &p[0], record->size); + } + return; + } + + if (record_flags & BtreeRecord::kBlobSizeSmall) { + record->size = sizeof(uint64_t); + if (direct_access) + record->data = &p[0]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &p[0], record->size); + } + return; + } + + uint64_t blob_id = *(uint64_t *)p; + + // the record is stored as a blob + LocalEnvironment *env = m_db->lenv(); + env->blob_manager()->read(context, blob_id, record, flags, arena); + } + + // Updates the record of a key + void set_record(Context *context, int slot, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index = 0) { + uint32_t chunk_offset = m_index.get_absolute_chunk_offset(slot); + uint32_t current_size = m_index.get_chunk_size(slot); + + // if the slot was not yet allocated: allocate new space, initialize + // it and then overwrite the record + if (current_size == 0) { + duplicate_index = 0; + flags |= HAM_OVERWRITE; + chunk_offset = m_index.allocate_space(m_node->get_count(), slot, 1 + 9); + chunk_offset = m_index.get_absolute_offset(chunk_offset); + // clear the record flags + m_data[chunk_offset] = 0; + m_data[chunk_offset + 1] = BtreeRecord::kBlobSizeEmpty; + + set_inline_record_count(slot, 1); + } + + // if there's no duplicate table, but we're not able to add another + // duplicate then offload all existing duplicates to a table + uint32_t record_count = get_inline_record_count(slot); + size_t required_size = 1 + (record_count + 1) * 9; + + if (!(m_data[chunk_offset] & BtreeRecord::kExtendedDuplicates) + && !(flags & HAM_OVERWRITE)) { + bool force_duptable = record_count >= m_duptable_threshold; + if (!force_duptable + && !m_index.can_allocate_space(m_node->get_count(), + required_size)) + force_duptable = true; + + // update chunk_offset - it might have been modified if + // m_index.can_allocate_space triggered a vacuumize() operation + chunk_offset = m_index.get_absolute_chunk_offset(slot); + + // already too many duplicates, or the record does not fit? then + // allocate an overflow duplicate list and move all duplicates to + // this list + if (force_duptable) { + DuplicateTable *dt = new DuplicateTable(m_db, !m_store_flags, + HAM_RECORD_SIZE_UNLIMITED); + uint64_t table_id = dt->create(context, get_record_data(slot, 0), + record_count); + if (!m_duptable_cache) + m_duptable_cache.reset(new DuplicateTableCache()); + (*m_duptable_cache)[table_id] = dt; + + // write the id of the duplicate table + if (m_index.get_chunk_size(slot) < 8 + 1) { + // do not erase the slot because it obviously occupies so + // little space + m_index.allocate_space(m_node->get_count(), slot, 8 + 1); + chunk_offset = m_index.get_absolute_chunk_offset(slot); + } + + m_data[chunk_offset] |= BtreeRecord::kExtendedDuplicates; + set_record_id(slot, table_id); + set_inline_record_count(slot, 0); + + m_index.set_chunk_size(slot, 10); + m_index.increase_vacuumize_counter(m_index.get_chunk_size(slot) - 10); + m_index.invalidate_next_offset(); + + // fall through + } + } + + // forward to duplicate table? + if (unlikely(m_data[chunk_offset] & BtreeRecord::kExtendedDuplicates)) { + uint64_t table_id = get_record_id(slot); + DuplicateTable *dt = get_duplicate_table(context, table_id); + uint64_t new_table_id = dt->set_record(context, duplicate_index, record, + flags, new_duplicate_index); + if (new_table_id != table_id) { + update_duplicate_table_id(dt, table_id, new_table_id); + set_record_id(slot, new_table_id); + } + return; + } + + uint64_t overwrite_blob_id = 0; + uint8_t *record_flags = 0; + uint8_t *p = 0; + + // the (inline) duplicate is overwritten + if (flags & HAM_OVERWRITE) { + record_flags = &m_data[chunk_offset + 1 + 9 * duplicate_index]; + p = record_flags + 1; + + // If a blob is overwritten with an inline record then the old blob + // has to be deleted + if (*record_flags == 0) { + if (record->size <= 8) { + uint64_t blob_id = *(uint64_t *)p; + if (blob_id) + m_db->lenv()->blob_manager()->erase(context, blob_id); + } + else + overwrite_blob_id = *(uint64_t *)p; + // fall through + } + // then jump to the code which performs the actual insertion + goto write_record; + } + + // Allocate new space for the duplicate table, if required + if (current_size < required_size) { + uint8_t *oldp = &m_data[chunk_offset]; + uint32_t old_chunk_size = m_index.get_chunk_size(slot); + uint32_t old_chunk_offset = m_index.get_chunk_offset(slot); + uint32_t new_chunk_offset = m_index.allocate_space(m_node->get_count(), + slot, required_size); + chunk_offset = m_index.get_absolute_offset(new_chunk_offset); + if (current_size > 0) + memmove(&m_data[chunk_offset], oldp, current_size); + if (old_chunk_offset != new_chunk_offset) + m_index.add_to_freelist(m_node->get_count(), old_chunk_offset, + old_chunk_size); + } + + // adjust flags + if (flags & HAM_DUPLICATE_INSERT_BEFORE && duplicate_index == 0) + flags |= HAM_DUPLICATE_INSERT_FIRST; + else if (flags & HAM_DUPLICATE_INSERT_AFTER) { + if (duplicate_index == (int)record_count) + flags |= HAM_DUPLICATE_INSERT_LAST; + else { + flags |= HAM_DUPLICATE_INSERT_BEFORE; + duplicate_index++; + } + } + + // handle overwrites or inserts/appends + if (flags & HAM_DUPLICATE_INSERT_FIRST) { + if (record_count > 0) { + uint8_t *ptr = &m_data[chunk_offset + 1]; + memmove(&m_data[chunk_offset + 1 + 9], ptr, record_count * 9); + } + duplicate_index = 0; + } + else if (flags & HAM_DUPLICATE_INSERT_BEFORE) { + memmove(&m_data[chunk_offset + 1 + 9 * (duplicate_index + 1)], + &m_data[chunk_offset + 1 + 9 * duplicate_index], + (record_count - duplicate_index) * 9); + } + else // HAM_DUPLICATE_INSERT_LAST + duplicate_index = record_count; + + set_inline_record_count(slot, record_count + 1); + + record_flags = &m_data[chunk_offset + 1 + 9 * duplicate_index]; + p = record_flags + 1; + +write_record: + if (record->size == 0) { + memcpy(p, "\0\0\0\0\0\0\0\0", 8); + *record_flags = BtreeRecord::kBlobSizeEmpty; + } + else if (record->size < sizeof(uint64_t)) { + p[sizeof(uint64_t) - 1] = (uint8_t)record->size; + memcpy(&p[0], record->data, record->size); + *record_flags = BtreeRecord::kBlobSizeTiny; + } + else if (record->size == sizeof(uint64_t)) { + memcpy(&p[0], record->data, record->size); + *record_flags = BtreeRecord::kBlobSizeSmall; + } + else { + LocalEnvironment *env = m_db->lenv(); + *record_flags = 0; + uint64_t blob_id; + if (overwrite_blob_id) + blob_id = env->blob_manager()->overwrite(context, + overwrite_blob_id, record, flags); + else + blob_id = env->blob_manager()->allocate(context, record, flags); + memcpy(p, &blob_id, sizeof(blob_id)); + } + + if (new_duplicate_index) + *new_duplicate_index = duplicate_index; + } + + // Erases a record + void erase_record(Context *context, int slot, int duplicate_index = 0, + bool all_duplicates = false) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + + // forward to external duplicate table? + if (unlikely(m_data[offset] & BtreeRecord::kExtendedDuplicates)) { + uint64_t table_id = get_record_id(slot); + DuplicateTable *dt = get_duplicate_table(context, table_id); + uint64_t new_table_id = dt->erase_record(context, duplicate_index, + all_duplicates); + if (new_table_id == 0) { + m_duptable_cache->erase(table_id); + set_record_id(slot, 0); + m_data[offset] &= ~BtreeRecord::kExtendedDuplicates; + delete dt; + } + else if (new_table_id != table_id) { + update_duplicate_table_id(dt, table_id, new_table_id); + set_record_id(slot, new_table_id); + } + return; + } + + // erase the last duplicate? + uint32_t count = get_inline_record_count(slot); + if (count == 1 && duplicate_index == 0) + all_duplicates = true; + + // adjust next_offset, if necessary. Note that get_next_offset() is + // called with a node_count of zero, which is valid (it avoids a + // recalculation in case there is no next_offset) + m_index.maybe_invalidate_next_offset(m_index.get_chunk_offset(slot) + + m_index.get_chunk_size(slot)); + + // erase all duplicates? + if (all_duplicates) { + for (uint32_t i = 0; i < count; i++) { + uint8_t *p = &m_data[offset + 1 + 9 * i]; + if (!is_record_inline(*p)) { + m_db->lenv()->blob_manager()->erase(context, *(uint64_t *)(p + 1)); + *(uint64_t *)(p + 1) = 0; + } + } + set_inline_record_count(slot, 0); + m_index.set_chunk_size(slot, 0); + } + else { + uint8_t *p = &m_data[offset + 1 + 9 * duplicate_index]; + if (!is_record_inline(*p)) { + m_db->lenv()->blob_manager()->erase(context, *(uint64_t *)(p + 1)); + *(uint64_t *)(p + 1) = 0; + } + if (duplicate_index < (int)count - 1) + memmove(&m_data[offset + 1 + 9 * duplicate_index], + &m_data[offset + 1 + 9 * (duplicate_index + 1)], + 9 * (count - duplicate_index - 1)); + set_inline_record_count(slot, count - 1); + } + } + + // Returns a record id + uint64_t get_record_id(int slot, + int duplicate_index = 0) const { + return (*(uint64_t *)get_record_data(slot, duplicate_index)); + } + + // Sets a record id + void set_record_id(int slot, uint64_t id) { + *(uint64_t *)get_record_data(slot, 0) = id; + } + + // Checks the integrity of this node. Throws an exception if there is a + // violation. + void check_integrity(Context *context, size_t node_count) const { + for (size_t i = 0; i < node_count; i++) { + uint32_t offset = m_index.get_absolute_chunk_offset(i); + if (m_data[offset] & BtreeRecord::kExtendedDuplicates) { + ham_assert((m_data[offset] & 0x7f) == 0); + } + } + + m_index.check_integrity(node_count); + } + + // Change the capacity; the capacity will be reduced, growing is not + // implemented. Which means that the data area must be copied; the offsets + // do not have to be changed. + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + // no capacity given? then try to find a good default one + if (capacity_hint == 0) { + capacity_hint = (new_range_size - m_index.get_next_offset(node_count) + - get_full_record_size()) / m_index.get_full_index_size(); + if (capacity_hint <= node_count) + capacity_hint = node_count + 1; + } + + // if there's not enough space for the new capacity then try to reduce + // the capacity + if (m_index.get_next_offset(node_count) + get_full_record_size() + + capacity_hint * m_index.get_full_index_size() + + UpfrontIndex::kPayloadOffset + > new_range_size) + capacity_hint = node_count + 1; + + m_index.change_range_size(node_count, new_data_ptr, new_range_size, + capacity_hint); + m_data = new_data_ptr; + m_range_size = new_range_size; + } + + // Returns true if there's not enough space for another record + bool requires_split(size_t node_count) { + // if the record is extremely small then make sure there's some headroom; + // this is required for DuplicateTable ids which are 64bit numbers + size_t required = get_full_record_size(); + if (required < 10) + required = 10; + return (m_index.requires_split(node_count, required)); + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseRecordList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_index, + m_index.get_capacity() * m_index.get_full_index_size()); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_unused, + m_range_size - get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) { + out << "(" << get_record_count(context, slot) << " records)"; + } + + private: + // Returns the number of records that are stored inline + uint32_t get_inline_record_count(int slot) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (m_data[offset] & 0x7f); + } + + // Sets the number of records that are stored inline + void set_inline_record_count(int slot, size_t count) { + ham_assert(count <= 0x7f); + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + m_data[offset] &= BtreeRecord::kExtendedDuplicates; + m_data[offset] |= count; + } + + // Returns a pointer to the record data (const flavour) + uint8_t *get_record_data(int slot, int duplicate_index = 0) { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (&m_data[offset + 1 + 9 * duplicate_index]); + } + + // Returns a pointer to the record data (const flavour) + const uint8_t *get_record_data(int slot, + int duplicate_index = 0) const { + uint32_t offset = m_index.get_absolute_chunk_offset(slot); + return (&m_data[offset + 1 + 9 * duplicate_index]); + } +}; + +} // namespace DefLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_RECORDS_DUPLICATE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_inline.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_inline.h new file mode 100644 index 0000000000..6a7ac4ff35 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_inline.h @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * RecordList for Inline Records + * + * Inline Records are records that are stored directly in the leaf node, and + * not in an external blob. Only for fixed length records. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_RECORDS_INLINE_H +#define HAM_BTREE_RECORDS_INLINE_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_node.h" +#include "3btree/btree_records_base.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The template classes in this file are wrapped in a separate namespace +// to avoid naming clashes with btree_impl_default.h +// +namespace PaxLayout { + +class InlineRecordList : public BaseRecordList +{ + public: + enum { + // A flag whether this RecordList has sequential data + kHasSequentialData = 1 + }; + + // Constructor + InlineRecordList(LocalDatabase *db, PBtreeNode *node) + : m_db(db), m_record_size(db->config().record_size), m_data(0) { + ham_assert(m_record_size != HAM_RECORD_SIZE_UNLIMITED); + } + + // Sets the data pointer + void create(uint8_t *data, size_t range_size) { + m_data = (uint8_t *)data; + m_range_size = range_size; + } + + // Opens an existing RecordList + void open(uint8_t *ptr, size_t range_size, size_t node_count) { + m_data = ptr; + m_range_size = range_size; + } + + // Returns the actual record size including overhead + size_t get_full_record_size() const { + return (m_record_size); + } + + // Calculates the required size for a range with the specified |capacity| + size_t get_required_range_size(size_t node_count) const { + return (node_count * m_record_size); + } + + // Returns the record counter of a key + int get_record_count(Context *context, int slot) const { + return (1); + } + + // Returns the record size + uint64_t get_record_size(Context *context, int slot, + int duplicate_index = 0) const { + return (m_record_size); + } + + // Returns the full record and stores it in |dest|; memory must be + // allocated by the caller + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index) const { + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + if (flags & HAM_PARTIAL) { + ham_trace(("flag HAM_PARTIAL is not allowed if record is " + "stored inline")); + throw Exception(HAM_INV_PARAMETER); + } + + // the record is stored inline + record->size = m_record_size; + + if (m_record_size == 0) + record->data = 0; + else if (direct_access) + record->data = &m_data[slot * m_record_size]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &m_data[slot * m_record_size], record->size); + } + } + + // Updates the record of a key + void set_record(Context *context, int slot, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index = 0) { + ham_assert(record->size == m_record_size); + // it's possible that the records have size 0 - then don't copy anything + if (m_record_size) + memcpy(&m_data[m_record_size * slot], record->data, m_record_size); + } + + // Erases the record + void erase_record(Context *context, int slot, int duplicate_index = 0, + bool all_duplicates = true) { + if (m_record_size) + memset(&m_data[m_record_size * slot], 0, m_record_size); + } + + // Erases a whole slot by shifting all larger records to the "left" + void erase(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count - 1) + memmove(&m_data[m_record_size * slot], + &m_data[m_record_size * (slot + 1)], + m_record_size * (node_count - slot - 1)); + } + + // Creates space for one additional record + void insert(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count) { + memmove(&m_data[m_record_size * (slot + 1)], + &m_data[m_record_size * slot], + m_record_size * (node_count - slot)); + } + memset(&m_data[m_record_size * slot], 0, m_record_size); + } + + // Copies |count| records from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, InlineRecordList &dest, + size_t other_count, int dstart) { + memcpy(&dest.m_data[m_record_size * dstart], + &m_data[m_record_size * sstart], + m_record_size * (node_count - sstart)); + } + + // Returns the record id. Not required for fixed length leaf nodes + uint64_t get_record_id(int slot, int duplicate_index = 0) + const { + ham_assert(!"shouldn't be here"); + return (0); + } + + // Sets the record id. Not required for fixed length leaf nodes + void set_record_id(int slot, uint64_t ptr) { + ham_assert(!"shouldn't be here"); + } + + // Returns true if there's not enough space for another record + bool requires_split(size_t node_count) const { + if (m_range_size == 0) + return (false); + return ((node_count + 1) * m_record_size >= m_range_size); + } + + // Change the capacity; for PAX layouts this just means copying the + // data from one place to the other + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + memmove(new_data_ptr, m_data, node_count * m_record_size); + m_data = new_data_ptr; + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseRecordList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_unused, + m_range_size - get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) const { + out << "(" << get_record_size(context, slot) << " bytes)"; + } + + private: + // The parent database of this btree + LocalDatabase *m_db; + + // The record size, as specified when the database was created + size_t m_record_size; + + // The actual record data + uint8_t *m_data; +}; + +} // namespace PaxLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_RECORDS_INLINE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_internal.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_internal.h new file mode 100644 index 0000000000..9773119991 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_records_internal.h @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Internal RecordList + * + * Only for records of internal nodes. Internal nodes only store page IDs, + * therefore this |InternalRecordList| is optimized for 64bit IDs + * (and is implemented as a uint64_t[] array). + * + * For file-based databases the page IDs are stored modulo page size, which + * results in smaller IDs. Small IDs can be compressed more efficiently + * (-> hamsterdb pro). + * + * In-memory based databases just store the raw pointers. + * + * @exception_safe: nothrow + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_RECORDS_INTERNAL_H +#define HAM_BTREE_RECORDS_INTERNAL_H + +#include "0root/root.h" + +#include <sstream> +#include <iostream> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" +#include "1base/dynamic_array.h" +#include "2page/page.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_records_base.h" +#include "3btree/btree_node.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The template classes in this file are wrapped in a separate namespace +// to avoid naming clashes with btree_impl_default.h +// +namespace PaxLayout { + +class InternalRecordList : public BaseRecordList +{ + public: + enum { + // A flag whether this RecordList has sequential data + kHasSequentialData = 1 + }; + + // Constructor + InternalRecordList(LocalDatabase *db, PBtreeNode *node) + : m_db(db), m_data(0) { + m_page_size = m_db->lenv()->config().page_size_bytes; + m_store_raw_id = (m_db->lenv()->config().flags + & HAM_IN_MEMORY) == HAM_IN_MEMORY; + } + + // Sets the data pointer + void create(uint8_t *data, size_t range_size) { + m_data = (uint64_t *)data; + m_range_size = range_size; + } + + // Opens an existing RecordList + void open(uint8_t *ptr, size_t range_size, size_t node_count) { + m_data = (uint64_t *)ptr; + m_range_size = range_size; + } + + // Returns the actual size including overhead + size_t get_full_record_size() const { + return (sizeof(uint64_t)); + } + + // Calculates the required size for a range with the specified |capacity| + size_t get_required_range_size(size_t node_count) const { + return (node_count * sizeof(uint64_t)); + } + + // Returns the record counter of a key; this implementation does not + // support duplicates, therefore the record count is always 1 + int get_record_count(Context *context, int slot) const { + return (1); + } + + // Returns the record size + uint64_t get_record_size(Context *context, int slot, + int duplicate_index = 0) const { + return (sizeof(uint64_t)); + } + + // Returns the full record and stores it in |dest|; memory must be + // allocated by the caller + void get_record(Context *context, int slot, ByteArray *arena, + ham_record_t *record, uint32_t flags, + int duplicate_index) const { + bool direct_access = (flags & HAM_DIRECT_ACCESS) != 0; + + // the record is stored inline + record->size = sizeof(uint64_t); + + if (direct_access) + record->data = (void *)&m_data[slot]; + else { + if ((record->flags & HAM_RECORD_USER_ALLOC) == 0) { + arena->resize(record->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, &m_data[slot], record->size); + } + } + + // Updates the record of a key + void set_record(Context *context, int slot, int duplicate_index, + ham_record_t *record, uint32_t flags, + uint32_t *new_duplicate_index = 0) { + ham_assert(record->size == sizeof(uint64_t)); + m_data[slot] = *(uint64_t *)record->data; + } + + // Erases the record + void erase_record(Context *context, int slot, int duplicate_index = 0, + bool all_duplicates = true) { + m_data[slot] = 0; + } + + // Erases a whole slot by shifting all larger records to the "left" + void erase(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count - 1) + memmove(&m_data[slot], &m_data[slot + 1], + sizeof(uint64_t) * (node_count - slot - 1)); + } + + // Creates space for one additional record + void insert(Context *context, size_t node_count, int slot) { + if (slot < (int)node_count) { + memmove(&m_data[slot + 1], &m_data[slot], + sizeof(uint64_t) * (node_count - slot)); + } + m_data[slot] = 0; + } + + // Copies |count| records from this[sstart] to dest[dstart] + void copy_to(int sstart, size_t node_count, InternalRecordList &dest, + size_t other_count, int dstart) { + memcpy(&dest.m_data[dstart], &m_data[sstart], + sizeof(uint64_t) * (node_count - sstart)); + } + + // Sets the record id + void set_record_id(int slot, uint64_t value) { + ham_assert(m_store_raw_id ? 1 : value % m_page_size == 0); + m_data[slot] = m_store_raw_id ? value : value / m_page_size; + } + + // Returns the record id + uint64_t get_record_id(int slot, + int duplicate_index = 0) const { + ham_assert(duplicate_index == 0); + return (m_store_raw_id ? m_data[slot] : m_page_size * m_data[slot]); + } + + // Returns true if there's not enough space for another record + bool requires_split(size_t node_count) const { + return ((node_count + 1) * sizeof(uint64_t) >= m_range_size); + } + + // Change the capacity; for PAX layouts this just means copying the + // data from one place to the other + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t capacity_hint) { + if ((uint64_t *)new_data_ptr != m_data) { + memmove(new_data_ptr, m_data, node_count * sizeof(uint64_t)); + m_data = (uint64_t *)new_data_ptr; + } + m_range_size = new_range_size; + } + + // Fills the btree_metrics structure + void fill_metrics(btree_metrics_t *metrics, size_t node_count) { + BaseRecordList::fill_metrics(metrics, node_count); + BtreeStatistics::update_min_max_avg(&metrics->recordlist_unused, + m_range_size - get_required_range_size(node_count)); + } + + // Prints a slot to |out| (for debugging) + void print(Context *context, int slot, std::stringstream &out) const { + out << "(" << get_record_id(slot); + } + + private: + // The parent database of this btree + LocalDatabase *m_db; + + // The record data is an array of page IDs + uint64_t *m_data; + + // The page size + size_t m_page_size; + + // Store page ID % page size or the raw page ID? + bool m_store_raw_id; +}; + +} // namespace PaxLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_RECORDS_INTERNAL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.cc new file mode 100644 index 0000000000..edd8c7b7a1 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.cc @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> +#include <stdio.h> + +// Always verify that a file of level N does not include headers > N! +#include "2page/page.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_index.h" +#include "3btree/btree_node_proxy.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +BtreeStatistics::BtreeStatistics() + : m_append_count(0), m_prepend_count(0) +{ + memset(&m_last_leaf_pages[0], 0, sizeof(m_last_leaf_pages)); + memset(&m_last_leaf_count[0], 0, sizeof(m_last_leaf_count)); + memset(&m_keylist_range_size[0], 0, sizeof(m_keylist_range_size)); + memset(&m_keylist_capacities[0], 0, sizeof(m_keylist_capacities)); +} + +void +BtreeStatistics::find_succeeded(Page *page) +{ + uint64_t old = m_last_leaf_pages[kOperationFind]; + if (old != page->get_address()) { + m_last_leaf_pages[kOperationFind] = 0; + m_last_leaf_count[kOperationFind] = 0; + } + else + m_last_leaf_count[kOperationFind]++; +} + +void +BtreeStatistics::find_failed() +{ + m_last_leaf_pages[kOperationFind] = 0; + m_last_leaf_count[kOperationFind] = 0; +} + +void +BtreeStatistics::insert_succeeded(Page *page, uint16_t slot) +{ + uint64_t old = m_last_leaf_pages[kOperationInsert]; + if (old != page->get_address()) { + m_last_leaf_pages[kOperationInsert] = page->get_address(); + m_last_leaf_count[kOperationInsert] = 0; + } + else + m_last_leaf_count[kOperationInsert]++; + + BtreeNodeProxy *node; + node = page->get_db()->btree_index()->get_node_from_page(page); + ham_assert(node->is_leaf()); + + if (!node->get_right() && slot == node->get_count() - 1) + m_append_count++; + else + m_append_count = 0; + + if (!node->get_left() && slot == 0) + m_prepend_count++; + else + m_prepend_count = 0; +} + +void +BtreeStatistics::insert_failed() +{ + m_last_leaf_pages[kOperationInsert] = 0; + m_last_leaf_count[kOperationInsert] = 0; + m_append_count = 0; + m_prepend_count = 0; +} + +void +BtreeStatistics::erase_succeeded(Page *page) +{ + uint64_t old = m_last_leaf_pages[kOperationErase]; + if (old != page->get_address()) { + m_last_leaf_pages[kOperationErase] = page->get_address(); + m_last_leaf_count[kOperationErase] = 0; + } + else + m_last_leaf_count[kOperationErase]++; +} + +void +BtreeStatistics::erase_failed() +{ + m_last_leaf_pages[kOperationErase] = 0; + m_last_leaf_count[kOperationErase] = 0; +} + +void +BtreeStatistics::reset_page(Page *page) +{ + for (int i = 0; i < kOperationMax; i++) { + m_last_leaf_pages[i] = 0; + m_last_leaf_count[i] = 0; + } +} + +BtreeStatistics::FindHints +BtreeStatistics::get_find_hints(uint32_t flags) +{ + BtreeStatistics::FindHints hints = {flags, flags, 0, false}; + + /* if the last 5 lookups hit the same page: reuse that page */ + if (m_last_leaf_count[kOperationFind] >= 5) { + hints.try_fast_track = true; + hints.leaf_page_addr = m_last_leaf_pages[kOperationFind]; + } + + return (hints); +} + +BtreeStatistics::InsertHints +BtreeStatistics::get_insert_hints(uint32_t flags) +{ + InsertHints hints = {flags, flags, 0, 0, 0, 0, 0}; + + /* if the previous insert-operation replaced the upper bound (or + * lower bound) key then it was actually an append (or prepend) operation. + * in this case there's some probability that the next operation is also + * appending/prepending. + */ + if (m_append_count > 0) + hints.flags |= HAM_HINT_APPEND; + else if (m_prepend_count > 0) + hints.flags |= HAM_HINT_PREPEND; + + hints.append_count = m_append_count; + hints.prepend_count = m_prepend_count; + + /* if the last 5 inserts hit the same page: reuse that page */ + if (m_last_leaf_count[kOperationInsert] >= 5) + hints.leaf_page_addr = m_last_leaf_pages[kOperationInsert]; + + return (hints); +} + +#define AVG(m) m._instances ? (m._total / m._instances) : 0 + +void +BtreeStatistics::finalize_metrics(btree_metrics_t *metrics) +{ + metrics->keys_per_page.avg = AVG(metrics->keys_per_page); + metrics->keylist_ranges.avg = AVG(metrics->keylist_ranges); + metrics->recordlist_ranges.avg = AVG(metrics->recordlist_ranges); + metrics->keylist_index.avg = AVG(metrics->keylist_index); + metrics->recordlist_index.avg = AVG(metrics->recordlist_index); + metrics->keylist_unused.avg = AVG(metrics->keylist_unused); + metrics->recordlist_unused.avg = AVG(metrics->recordlist_unused); + metrics->keylist_blocks_per_page.avg = AVG(metrics->keylist_blocks_per_page); + metrics->keylist_block_sizes.avg = AVG(metrics->keylist_block_sizes); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.h new file mode 100644 index 0000000000..66c3f21ab9 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_stats.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree find/insert/erase statistical structures, functions and macros + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_STATS_H +#define HAM_BTREE_STATS_H + +#include "0root/root.h" + +#include <limits> + +#include "ham/hamsterdb_int.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Page; + +class BtreeStatistics { + public: + // Indices into find/insert/erase specific statistics + enum { + kOperationFind = 0, + kOperationInsert = 1, + kOperationErase = 2, + kOperationMax = 3 + }; + + struct FindHints { + // the original flags of ham_find + uint32_t original_flags; + + // the modified flags + uint32_t flags; + + // page/btree leaf to check first + uint64_t leaf_page_addr; + + // check specified btree leaf node page first + bool try_fast_track; + }; + + struct InsertHints { + // the original flags of ham_insert + uint32_t original_flags; + + // the modified flags + uint32_t flags; + + // page/btree leaf to check first + uint64_t leaf_page_addr; + + // the processed leaf page + Page *processed_leaf_page; + + // the slot in that page + uint16_t processed_slot; + + // count the number of appends + size_t append_count; + + // count the number of prepends + size_t prepend_count; + }; + + // Constructor + BtreeStatistics(); + + // Returns the btree hints for ham_find + FindHints get_find_hints(uint32_t flags); + + // Returns the btree hints for insert + InsertHints get_insert_hints(uint32_t flags); + + // Reports that a ham_find/ham_cusor_find succeeded + void find_succeeded(Page *page); + + // Reports that a ham_find/ham_cursor_find failed + void find_failed(); + + // Reports that a ham_insert/ham_cursor_insert succeeded + void insert_succeeded(Page *page, uint16_t slot); + + // Reports that a ham_insert/ham_cursor_insert failed + void insert_failed(); + + // Reports that a ham_erase/ham_cusor_erase succeeded + void erase_succeeded(Page *page); + + // Reports that a ham_erase/ham_cursor_erase failed + void erase_failed(); + + // Resets the statistics for a single page + void reset_page(Page *page); + + // Keep track of the KeyList range size + void set_keylist_range_size(bool leaf, size_t size) { + m_keylist_range_size[(int)leaf] = size; + } + + // Retrieves the KeyList range size + size_t get_keylist_range_size(bool leaf) const { + return (m_keylist_range_size[(int)leaf]); + } + + // Keep track of the KeyList capacities + void set_keylist_capacities(bool leaf, size_t capacity) { + m_keylist_capacities[(int)leaf] = capacity; + } + + // Retrieves the KeyList capacities size + size_t get_keylist_capacities(bool leaf) const { + return (m_keylist_capacities[(int)leaf]); + } + + // Calculate the "average" values + static void finalize_metrics(btree_metrics_t *metrics); + + // Update a min_max_avg structure + static void update_min_max_avg(min_max_avg_u32_t *data, uint32_t value) { + // first update? then perform initialization + if (data->_instances == 0) + data->min = std::numeric_limits<uint32_t>::max(); + + if (data->min > value) + data->min = value; + if (data->max < value) + data->max = value; + data->_total += value; + data->_instances++; + } + + private: + // last leaf page for find/insert/erase + uint64_t m_last_leaf_pages[kOperationMax]; + + // count of how often this leaf page was used + size_t m_last_leaf_count[kOperationMax]; + + // count the number of appends + size_t m_append_count; + + // count the number of prepends + size_t m_prepend_count; + + // the range size of the KeyList + size_t m_keylist_range_size[2]; + + // the capacities of the KeyList + size_t m_keylist_capacities[2]; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_STATS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.cc new file mode 100644 index 0000000000..07d6cf61d4 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.cc @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "3page_manager/page_manager.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_stats.h" +#include "3btree/btree_index.h" +#include "3btree/btree_update.h" +#include "3btree/btree_node_proxy.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* a unittest hook triggered when a page is split */ +void (*g_BTREE_INSERT_SPLIT_HOOK)(void); + +// Traverses the tree, looking for the leaf with the specified |key|. Will +// split or merge nodes while descending. +// Returns the leaf page and the |parent| of the leaf (can be null if +// there is no parent). +Page * +BtreeUpdateAction::traverse_tree(const ham_key_t *key, + BtreeStatistics::InsertHints &hints, + Page **parent) +{ + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + Page *page = env->page_manager()->fetch(m_context, + m_btree->get_root_address()); + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + *parent = 0; + + // if the root page is empty with children then collapse it + if (node->get_count() == 0 && !node->is_leaf()) { + page = collapse_root(page); + node = m_btree->get_node_from_page(page); + } + + int slot; + + // now walk down the tree + while (!node->is_leaf()) { + // is a split required? + if (node->requires_split(m_context)) { + page = split_page(page, *parent, key, hints); + node = m_btree->get_node_from_page(page); + } + + // get the child page + Page *sib_page = 0; + Page *child_page = m_btree->find_child(m_context, page, key, 0, &slot); + BtreeNodeProxy *child_node = m_btree->get_node_from_page(child_page); + + // We can merge this child with the RIGHT sibling iff... + // 1. it's not the right-most slot (and therefore the right sibling has + // the same parent as the child) + // 2. the child is a leaf! + // 3. it's empty or has too few elements + // 4. its right sibling is also empty + if (slot < (int)node->get_count() - 1 + && child_node->is_leaf() + && child_node->requires_merge() + && child_node->get_right() != 0) { + sib_page = env->page_manager()->fetch(m_context, + child_node->get_right(), + PageManager::kOnlyFromCache); + if (sib_page != 0) { + BtreeNodeProxy *sib_node = m_btree->get_node_from_page(sib_page); + if (sib_node->requires_merge()) { + merge_page(child_page, sib_page); + // also remove the link to the sibling from the parent + node->erase(m_context, slot + 1); + page->set_dirty(true); + } + } + } + + // We can also merge this child with the LEFT sibling iff... + // 1. it's not the left-most slot + // 2. the child is a leaf! + // 3. it's empty or has too few elements + // 4. its left sibling is also empty + else if (slot > 0 + && child_node->is_leaf() + && child_node->requires_merge() + && child_node->get_left() != 0) { + sib_page = env->page_manager()->fetch(m_context, + child_node->get_left(), + PageManager::kOnlyFromCache); + if (sib_page != 0) { + BtreeNodeProxy *sib_node = m_btree->get_node_from_page(sib_page); + if (sib_node->requires_merge()) { + merge_page(sib_page, child_page); + // also remove the link to the sibling from the parent + node->erase(m_context, slot); + page->set_dirty(true); + // continue traversal with the sibling + child_page = sib_page; + child_node = sib_node; + } + } + } + + *parent = page; + + // go down one level in the tree + page = child_page; + node = child_node; + } + + return (page); +} + +Page * +BtreeUpdateAction::merge_page(Page *page, Page *sibling) +{ + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + BtreeNodeProxy *sib_node = m_btree->get_node_from_page(sibling); + + if (sib_node->is_leaf()) + BtreeCursor::uncouple_all_cursors(m_context, sibling, 0); + + node->merge_from(m_context, sib_node); + page->set_dirty(true); + + // fix the linked list + node->set_right(sib_node->get_right()); + if (node->get_right()) { + Page *new_right = env->page_manager()->fetch(m_context, node->get_right()); + BtreeNodeProxy *new_right_node = m_btree->get_node_from_page(new_right); + new_right_node->set_left(page->get_address()); + new_right->set_dirty(true); + } + + m_btree->get_statistics()->reset_page(sibling); + m_btree->get_statistics()->reset_page(page); + env->page_manager()->del(m_context, sibling); + + BtreeIndex::ms_btree_smo_merge++; + return (page); +} + +Page * +BtreeUpdateAction::collapse_root(Page *root_page) +{ + LocalEnvironment *env = root_page->get_db()->lenv(); + BtreeNodeProxy *node = m_btree->get_node_from_page(root_page); + ham_assert(node->get_count() == 0); + + m_btree->get_statistics()->reset_page(root_page); + m_btree->set_root_address(m_context, node->get_ptr_down()); + Page *header = env->page_manager()->fetch(m_context, 0); + header->set_dirty(true); + + Page *new_root = env->page_manager()->fetch(m_context, + m_btree->get_root_address()); + new_root->set_type(Page::kTypeBroot); + env->page_manager()->del(m_context, root_page); + return (new_root); +} + +Page * +BtreeUpdateAction::split_page(Page *old_page, Page *parent, + const ham_key_t *key, + BtreeStatistics::InsertHints &hints) +{ + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + m_btree->get_statistics()->reset_page(old_page); + BtreeNodeProxy *old_node = m_btree->get_node_from_page(old_page); + + /* allocate a new page and initialize it */ + Page *new_page = env->page_manager()->alloc(m_context, Page::kTypeBindex); + { + PBtreeNode *node = PBtreeNode::from_page(new_page); + node->set_flags(old_node->is_leaf() ? PBtreeNode::kLeafNode : 0); + } + BtreeNodeProxy *new_node = m_btree->get_node_from_page(new_page); + + /* no parent page? then we're splitting the root page. allocate + * a new root page */ + if (!parent) + parent = allocate_new_root(old_page); + + Page *to_return = 0; + ByteArray pivot_key_arena; + ham_key_t pivot_key = {0}; + + /* if the key is appended then don't split the page; simply allocate + * a new page and insert the new key. */ + int pivot = 0; + if (hints.flags & HAM_HINT_APPEND && old_node->is_leaf()) { + int cmp = old_node->compare(m_context, key, old_node->get_count() - 1); + if (cmp == +1) { + to_return = new_page; + pivot_key = *key; + pivot = old_node->get_count(); + } + } + + /* no append? then calculate the pivot key and perform the split */ + if (pivot != (int)old_node->get_count()) { + pivot = get_pivot(old_node, key, hints); + + /* and store the pivot key for later */ + old_node->get_key(m_context, pivot, &pivot_key_arena, &pivot_key); + + /* leaf page: uncouple all cursors */ + if (old_node->is_leaf()) + BtreeCursor::uncouple_all_cursors(m_context, old_page, pivot); + /* internal page: fix the ptr_down of the new page + * (it must point to the ptr of the pivot key) */ + else + new_node->set_ptr_down(old_node->get_record_id(m_context, pivot)); + + /* now move some of the key/rid-tuples to the new page */ + old_node->split(m_context, new_node, pivot); + + // if the new key is >= the pivot key then continue with the right page, + // otherwise continue with the left page + to_return = m_btree->compare_keys((ham_key_t *)key, &pivot_key) >= 0 + ? new_page + : old_page; + } + + /* update the parent page */ + BtreeNodeProxy *parent_node = m_btree->get_node_from_page(parent); + uint64_t rid = new_page->get_address(); + ham_record_t record = ham_make_record(&rid, sizeof(rid)); + ham_status_t st = insert_in_page(parent, &pivot_key, &record, hints); + if (st) + throw Exception(st); + /* new root page? then also set ptr_down! */ + if (parent_node->get_count() == 0) + parent_node->set_ptr_down(old_page->get_address()); + + /* fix the double-linked list of pages, and mark the pages as dirty */ + if (old_node->get_right()) { + Page *sib_page = env->page_manager()->fetch(m_context, + old_node->get_right()); + BtreeNodeProxy *sib_node = m_btree->get_node_from_page(sib_page); + sib_node->set_left(new_page->get_address()); + sib_page->set_dirty(true); + } + new_node->set_left(old_page->get_address()); + new_node->set_right(old_node->get_right()); + old_node->set_right(new_page->get_address()); + new_page->set_dirty(true); + old_page->set_dirty(true); + + BtreeIndex::ms_btree_smo_split++; + + if (g_BTREE_INSERT_SPLIT_HOOK) + g_BTREE_INSERT_SPLIT_HOOK(); + + return (to_return); +} + +Page * +BtreeUpdateAction::allocate_new_root(Page *old_root) +{ + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + Page *new_root = env->page_manager()->alloc(m_context, Page::kTypeBroot); + + /* insert the pivot element and set ptr_down */ + BtreeNodeProxy *new_node = m_btree->get_node_from_page(new_root); + new_node->set_ptr_down(old_root->get_address()); + + m_btree->set_root_address(m_context, new_root->get_address()); + Page *header = env->page_manager()->fetch(m_context, 0); + header->set_dirty(true); + + old_root->set_type(Page::kTypeBindex); + + return (new_root); +} + +int +BtreeUpdateAction::get_pivot(BtreeNodeProxy *old_node, const ham_key_t *key, + BtreeStatistics::InsertHints &hints) const +{ + uint32_t old_count = old_node->get_count(); + ham_assert(old_count > 2); + + bool pivot_at_end = false; + if (hints.flags & HAM_HINT_APPEND && hints.append_count > 5) + pivot_at_end = true; + else if (old_node->get_right() == 0) { + int cmp = old_node->compare(m_context, key, old_node->get_count() - 1); + if (cmp > 0) + pivot_at_end = true; + } + + /* The position of the pivot key depends on the previous inserts; if most + * of them were appends then pick a pivot key at the "end" of the node */ + int pivot; + if (pivot_at_end || hints.append_count > 30) + pivot = old_count - 2; + else if (hints.append_count > 10) + pivot = (int)(old_count / 100.f * 66); + else if (hints.prepend_count > 10) + pivot = (int)(old_count / 100.f * 33); + else if (hints.prepend_count > 30) + pivot = 2; + else + pivot = old_count / 2; + + ham_assert(pivot > 0 && pivot <= (int)old_count - 2); + + return (pivot); +} + +ham_status_t +BtreeUpdateAction::insert_in_page(Page *page, ham_key_t *key, + ham_record_t *record, + BtreeStatistics::InsertHints &hints, + bool force_prepend, bool force_append) +{ + bool exists = false; + + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + + int flags = 0; + if (force_prepend) + flags |= PBtreeNode::kInsertPrepend; + if (force_append) + flags |= PBtreeNode::kInsertAppend; + + PBtreeNode::InsertResult result = node->insert(m_context, key, flags); + switch (result.status) { + case HAM_DUPLICATE_KEY: + if (hints.flags & HAM_OVERWRITE) { + /* key already exists; only overwrite the data */ + if (!node->is_leaf()) + return (HAM_SUCCESS); + } + else if (!(hints.flags & HAM_DUPLICATE)) + return (HAM_DUPLICATE_KEY); + /* do NOT shift keys up to make room; just overwrite the + * current [slot] */ + exists = true; + break; + case HAM_SUCCESS: + break; + default: + return (result.status); + } + + uint32_t new_duplicate_id = 0; + if (exists) { + if (node->is_leaf()) { + // overwrite record blob + node->set_record(m_context, result.slot, record, m_duplicate_index, + hints.flags, &new_duplicate_id); + + hints.processed_leaf_page = page; + hints.processed_slot = result.slot; + } + else { + // overwrite record id + ham_assert(record->size == sizeof(uint64_t)); + node->set_record_id(m_context, result.slot, *(uint64_t *)record->data); + } + } + // key does not exist and has to be inserted or appended + else { + try { + if (node->is_leaf()) { + // allocate record id + node->set_record(m_context, result.slot, record, m_duplicate_index, + hints.flags, &new_duplicate_id); + + hints.processed_leaf_page = page; + hints.processed_slot = result.slot; + } + else { + // set the internal record id + ham_assert(record->size == sizeof(uint64_t)); + node->set_record_id(m_context, result.slot, *(uint64_t *)record->data); + } + } + // In case of an error: undo the insert. This happens very rarely but + // it's possible, i.e. if the BlobManager fails to allocate storage. + catch (Exception &ex) { + if (result.slot < (int)node->get_count()) + node->erase(m_context, result.slot); + throw ex; + } + } + + page->set_dirty(true); + + // if this update was triggered with a cursor (and this is a leaf node): + // couple it to the inserted key + // TODO only when performing an insert(), not an erase()! + if (m_cursor && node->is_leaf()) { + m_cursor->get_parent()->set_to_nil(Cursor::kBtree); + ham_assert(m_cursor->get_state() == BtreeCursor::kStateNil); + m_cursor->couple_to_page(page, result.slot, new_duplicate_id); + } + + return (HAM_SUCCESS); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.h new file mode 100644 index 0000000000..51176980fe --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_update.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_BTREE_UPDATE_H +#define HAM_BTREE_UPDATE_H + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class BtreeIndex; +class BtreeCursor; + +/* + * Base class for updates; derived for erasing and inserting keys. + */ +class BtreeUpdateAction +{ + public: + // Constructor + BtreeUpdateAction(BtreeIndex *btree, Context *context, BtreeCursor *cursor, + uint32_t duplicate_index) + : m_btree(btree), m_context(context), m_cursor(cursor), + m_duplicate_index(duplicate_index) { + } + + // Traverses the tree, looking for the leaf with the specified |key|. Will + // split or merge nodes while descending. + // Returns the leaf page and the |parent| of the leaf (can be null if + // there is no parent). + Page *traverse_tree(const ham_key_t *key, + BtreeStatistics::InsertHints &hints, Page **parent); + + // Calculates the pivot index of a split. + // + // For databases with sequential access (this includes recno databases): + // do not split in the middle, but at the very end of the page. + // + // If this page is the right-most page in the index, and the new key is + // inserted at the very end, then we select the same pivot as for + // sequential access. + int get_pivot(BtreeNodeProxy *old_node, const ham_key_t *key, + BtreeStatistics::InsertHints &hints) const; + + // Splits |page| and updates the |parent|. If |parent| is null then + // it's assumed that |page| is the root node. + // Returns the new page in the path for |key|; caller can immediately + // continue the traversal. + Page *split_page(Page *old_page, Page *parent, const ham_key_t *key, + BtreeStatistics::InsertHints &hints); + + // Allocates a new root page and sets it up in the btree + Page *allocate_new_root(Page *old_root); + + // Inserts a key in a page + ham_status_t insert_in_page(Page *page, ham_key_t *key, + ham_record_t *record, + BtreeStatistics::InsertHints &hints, + bool force_prepend = false, bool force_append = false); + + protected: + // the current btree + BtreeIndex *m_btree; + + // The caller's Context + Context *m_context; + + // the current cursor + BtreeCursor *m_cursor; + + // the duplicate index (in case the update is for a duplicate key) + // 1-based (if 0 then this update is not for a duplicate) + uint32_t m_duplicate_index; + + private: + /* Merges the |sibling| into |page|, returns the merged page and moves + * the sibling to the freelist */ + Page *merge_page(Page *page, Page *sibling); + + /* collapse the root node; returns the new root */ + Page *collapse_root(Page *root_page); +}; + +} // namespace hamsterdb + +#endif // HAM_BTREE_UPDATE_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visit.cc b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visit.cc new file mode 100644 index 0000000000..05cd2603e5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visit.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * btree enumeration; visits each node + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3page_manager/page_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_node_proxy.h" +#include "3btree/btree_visitor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class BtreeVisitAction +{ + public: + BtreeVisitAction(BtreeIndex *btree, Context *context, BtreeVisitor &visitor, + bool visit_internal_nodes) + : m_btree(btree), m_context(context), m_visitor(visitor), + m_visit_internal_nodes(visit_internal_nodes) { + ham_assert(m_btree->get_root_address() != 0); + } + + void run() { + LocalDatabase *db = m_btree->get_db(); + LocalEnvironment *env = db->lenv(); + + uint32_t pm_flags = 0; + if (m_visitor.is_read_only()) + pm_flags = PageManager::kReadOnly; + + // get the root page of the tree + Page *page = env->page_manager()->fetch(m_context, + m_btree->get_root_address(), pm_flags); + + // go down to the leaf + while (page) { + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + uint64_t ptr_down = node->get_ptr_down(); + + // visit internal nodes as well? + if (ptr_down != 0 && m_visit_internal_nodes) { + while (page) { + node = m_btree->get_node_from_page(page); + m_visitor(m_context, node); + + // load the right sibling + uint64_t right = node->get_right(); + if (right) + page = env->page_manager()->fetch(m_context, right, pm_flags); + else + page = 0; + } + } + + // follow the pointer to the smallest child + if (ptr_down) + page = env->page_manager()->fetch(m_context, ptr_down, pm_flags); + else + break; + } + + ham_assert(page != 0); + + // now visit all leaf nodes + while (page) { + BtreeNodeProxy *node = m_btree->get_node_from_page(page); + uint64_t right = node->get_right(); + + m_visitor(m_context, node); + + /* follow the pointer to the right sibling */ + if (right) + page = env->page_manager()->fetch(m_context, right, pm_flags); + else + break; + } + } + + private: + BtreeIndex *m_btree; + Context *m_context; + BtreeVisitor &m_visitor; + bool m_visit_internal_nodes; +}; + +void +BtreeIndex::visit_nodes(Context *context, BtreeVisitor &visitor, + bool visit_internal_nodes) +{ + BtreeVisitAction bva(this, context, visitor, visit_internal_nodes); + bva.run(); +} + +} // namespace hamsterdb + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visitor.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visitor.h new file mode 100644 index 0000000000..19770a9e70 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/btree_visitor.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_BTREE_VISITOR_H +#define HAM_BTREE_VISITOR_H + +#include "0root/root.h" + +#include "ham/hamsterdb_ola.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The ScanVisitor is the callback implementation for the scan call. +// It will either receive single keys or multiple keys in an array. +// +struct ScanVisitor { + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) = 0; + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) = 0; + + // Assigns the internal result to |result| + virtual void assign_result(hola_result_t *result) = 0; +}; + +struct Context; +class BtreeNodeProxy; + +// +// The BtreeVisitor is the callback implementation for the visit call. +// It will visit each node instead of each key. +// +struct BtreeVisitor { + // Specifies if the visitor modifies the node + virtual bool is_read_only() const = 0; + + // called for each node + virtual void operator()(Context *context, BtreeNodeProxy *node) = 0; +}; + +} // namespace hamsterdb + +#endif /* HAM_BTREE_VISITOR_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3btree/upfront_index.h b/plugins/Dbx_kv/src/hamsterdb/src/3btree/upfront_index.h new file mode 100644 index 0000000000..b8aad1396d --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3btree/upfront_index.h @@ -0,0 +1,684 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A small index which manages variable length buffers. Used to manage + * variable length keys or records. + * + * The UpfrontIndex manages a range of bytes, organized in variable length + * |chunks|, assigned at initialization time when calling |allocate()| + * or |open()|. + * + * These chunks are organized in |slots|, each slot stores the offset and + * the size of the chunk data. The offset is stored as 16- or 32-bit, depending + * on the page size. The size is always a 16bit integer. + * + * The number of used slots is not stored in the UpfrontIndex, since it is + * already managed in the caller (this is equal to |PBtreeNode::get_count()|). + * Therefore you will see a lot of methods receiving a |node_count| parameter. + * + * Deleted chunks are moved to a |freelist|, which is simply a list of slots + * directly following those slots that are in use. + * + * In addition, the UpfrontIndex keeps track of the unused space at the end + * of the range (via |get_next_offset()|), in order to allow a fast + * allocation of space. + * + * The UpfrontIndex stores metadata at the beginning: + * [0..3] freelist count + * [4..7] next offset + * [8..11] capacity + * + * Data is stored in the following layout: + * |metadata|slot1|slot2|...|slotN|free1|free2|...|freeM|data1|data2|...|dataN| + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_BTREE_UPFRONT_INDEX_H +#define HAM_BTREE_UPFRONT_INDEX_H + +#include "0root/root.h" + +#include <algorithm> +#include <vector> + +// Always verify that a file of level N does not include headers > N! +#include "1globals/globals.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +namespace DefLayout { + +/* + * A helper class to sort ranges; used during validation of the up-front + * index in check_index_integrity() + */ +struct SortHelper { + uint32_t offset; + int slot; + + bool operator<(const SortHelper &rhs) const { + return (offset < rhs.offset); + } +}; + +static bool +sort_by_offset(const SortHelper &lhs, const SortHelper &rhs) { + return (lhs.offset < rhs.offset); +} + +class UpfrontIndex +{ + enum { + // width of the 'size' field + kSizeofSize = 1 // 1 byte - max chunk size is 255 + }; + + public: + enum { + // for freelist_count, next_offset, capacity + kPayloadOffset = 12, + + // minimum capacity of the index + kMinimumCapacity = 16 + }; + + // Constructor; creates an empty index which needs to be initialized + // with |create()| or |open()|. + UpfrontIndex(LocalDatabase *db) + : m_data(0), m_range_size(0), m_vacuumize_counter(0) { + size_t page_size = db->lenv()->config().page_size_bytes; + if (page_size <= 64 * 1024) + m_sizeof_offset = 2; + else + m_sizeof_offset = 4; + } + + // Initialization routine; sets data pointer, range size and the + // initial capacity. + void create(uint8_t *data, size_t range_size, size_t capacity) { + m_data = data; + m_range_size = range_size; + set_capacity(capacity); + clear(); + } + + // "Opens" an existing index from memory. This method sets the data + // pointer and initializes itself. + void open(uint8_t *data, size_t range_size) { + m_data = data; + m_range_size = range_size; + // the vacuumize-counter is not persisted, therefore + // pretend that the counter is very high; in worst case this will cause + // an invalid call to vacuumize(), which is not a problem + if (get_freelist_count()) + m_vacuumize_counter = m_range_size; + } + + // Changes the range size and capacity of the index; used to resize the + // KeyList or RecordList + void change_range_size(size_t node_count, uint8_t *new_data_ptr, + size_t new_range_size, size_t new_capacity) { + if (!new_data_ptr) + new_data_ptr = m_data; + if (!new_range_size) + new_range_size = m_range_size; + + // get rid of the freelist and collect the garbage + if (get_freelist_count() > 0) + vacuumize(node_count); + ham_assert(get_freelist_count() == 0); + + size_t used_data_size = get_next_offset(node_count); + size_t old_capacity = get_capacity(); + uint8_t *src = &m_data[kPayloadOffset + + old_capacity * get_full_index_size()]; + uint8_t *dst = &new_data_ptr[kPayloadOffset + + new_capacity * get_full_index_size()]; + + // if old range == new range then leave + if (m_range_size == new_range_size + && old_capacity == new_capacity + && m_data == new_data_ptr ) + return; + + ham_assert(dst - new_data_ptr + used_data_size <= new_range_size); + + // shift "to the right"? Then first move the data and afterwards + // the index + if (dst > src) { + memmove(dst, src, used_data_size); + memmove(new_data_ptr, m_data, + kPayloadOffset + new_capacity * get_full_index_size()); + } + // vice versa otherwise + else if (dst <= src) { + if (new_data_ptr != m_data) + memmove(new_data_ptr, m_data, + kPayloadOffset + new_capacity * get_full_index_size()); + memmove(dst, src, used_data_size); + } + + m_data = new_data_ptr; + m_range_size = new_range_size; + set_capacity(new_capacity); + set_freelist_count(0); + set_next_offset(used_data_size); // has dependency to get_freelist_count() + } + + // Calculates the required size for a range + size_t get_required_range_size(size_t node_count) const { + return (UpfrontIndex::kPayloadOffset + + get_capacity() * get_full_index_size() + + get_next_offset(node_count)); + } + + // Returns the size of a single index entry + size_t get_full_index_size() const { + return (m_sizeof_offset + kSizeofSize); + } + + // Transforms a relative offset of the payload data to an absolute offset + // in |m_data| + uint32_t get_absolute_offset(uint32_t offset) const { + return (offset + + kPayloadOffset + + get_capacity() * get_full_index_size()); + } + + // Returns the absolute start offset of a chunk + uint32_t get_absolute_chunk_offset(int slot) const { + return (get_absolute_offset(get_chunk_offset(slot))); + } + + // Returns the relative start offset of a chunk + uint32_t get_chunk_offset(int slot) const { + uint8_t *p = &m_data[kPayloadOffset + get_full_index_size() * slot]; + if (m_sizeof_offset == 2) + return (*(uint16_t *)p); + else { + ham_assert(m_sizeof_offset == 4); + return (*(uint32_t *)p); + } + } + + // Returns the size of a chunk + uint16_t get_chunk_size(int slot) const { + return (m_data[kPayloadOffset + get_full_index_size() * slot + + m_sizeof_offset]); + } + + // Sets the size of a chunk (does NOT actually resize the chunk!) + void set_chunk_size(int slot, uint16_t size) { + ham_assert(size <= 255); + m_data[kPayloadOffset + get_full_index_size() * slot + m_sizeof_offset] + = (uint8_t)size; + } + + // Increases the "vacuumize-counter", which is an indicator whether + // rearranging the node makes sense + void increase_vacuumize_counter(size_t gap_size) { + m_vacuumize_counter += gap_size; + } + + // Vacuumizes the index, *if it makes sense*. Returns true if the + // operation was successful, otherwise false + bool maybe_vacuumize(size_t node_count) { + if (m_vacuumize_counter > 0 || get_freelist_count() > 0) { + vacuumize(node_count); + return (true); + } + return (false); + } + + // Returns true if this index has at least one free slot available. + // |node_count| is the number of used slots (this is managed by the caller) + bool can_insert(size_t node_count) { + return (likely(node_count + get_freelist_count() < get_capacity())); + } + + // Inserts a slot at the position |slot|. |node_count| is the number of + // used slots (this is managed by the caller) + void insert(size_t node_count, int slot) { + ham_assert(can_insert(node_count) == true); + + size_t slot_size = get_full_index_size(); + size_t total_count = node_count + get_freelist_count(); + uint8_t *p = &m_data[kPayloadOffset + slot_size * slot]; + if (total_count > 0 && slot < (int)total_count) { + // create a gap in the index + memmove(p + slot_size, p, slot_size * (total_count - slot)); + } + + // now fill the gap + memset(p, 0, slot_size); + } + + // Erases a slot at the position |slot| + // |node_count| is the number of used slots (this is managed by the caller) + void erase(size_t node_count, int slot) { + size_t slot_size = get_full_index_size(); + size_t total_count = node_count + get_freelist_count(); + + ham_assert(slot < (int)total_count); + + set_freelist_count(get_freelist_count() + 1); + + size_t chunk_size = get_chunk_size(slot); + + increase_vacuumize_counter(chunk_size); + + // nothing to do if we delete the very last (used) slot; the freelist + // counter was already incremented, the used counter is decremented + // by the caller + if (slot == (int)node_count - 1) + return; + + size_t chunk_offset = get_chunk_offset(slot); + + // shift all items to the left + uint8_t *p = &m_data[kPayloadOffset + slot_size * slot]; + memmove(p, p + slot_size, slot_size * (total_count - slot)); + + // then copy the deleted chunk to the freelist + set_chunk_offset(total_count - 1, chunk_offset); + set_chunk_size(total_count - 1, chunk_size); + } + + // Adds a chunk to the freelist. Will not do anything if the node + // is already full. + void add_to_freelist(size_t node_count, uint32_t chunk_offset, + uint32_t chunk_size) { + size_t total_count = node_count + get_freelist_count(); + if (likely(total_count < get_capacity())) { + set_freelist_count(get_freelist_count() + 1); + set_chunk_size(total_count, chunk_size); + set_chunk_offset(total_count, chunk_offset); + } + } + + // Returns true if this page has enough space to store at least |num_bytes| + // bytes. + bool can_allocate_space(size_t node_count, size_t num_bytes) { + // first check if we can append the data; this is the cheapest check, + // therefore it comes first + if (get_next_offset(node_count) + num_bytes <= get_usable_data_size()) + return (true); + + // otherwise check the freelist + uint32_t total_count = node_count + get_freelist_count(); + for (uint32_t i = node_count; i < total_count; i++) + if (get_chunk_size(i) >= num_bytes) + return (true); + return (false); + } + + // Allocates space for a |slot| and returns the offset of that chunk + uint32_t allocate_space(size_t node_count, int slot, + size_t num_bytes) { + ham_assert(can_allocate_space(node_count, num_bytes)); + + size_t next_offset = get_next_offset(node_count); + + // try to allocate space at the end of the node + if (next_offset + num_bytes <= get_usable_data_size()) { + uint32_t offset = get_chunk_offset(slot); + // if this slot's data is at the very end then maybe it can be + // resized without actually moving the data + if (unlikely(next_offset == offset + get_chunk_size(slot))) { + set_next_offset(offset + num_bytes); + set_chunk_size(slot, num_bytes); + return (offset); + } + set_next_offset(next_offset + num_bytes); + set_chunk_offset(slot, next_offset); + set_chunk_size(slot, num_bytes); + return (next_offset); + } + + size_t slot_size = get_full_index_size(); + + // otherwise check the freelist + uint32_t total_count = node_count + get_freelist_count(); + for (uint32_t i = node_count; i < total_count; i++) { + uint32_t chunk_size = get_chunk_size(i); + uint32_t chunk_offset = get_chunk_offset(i); + if (chunk_size >= num_bytes) { + // update next_offset? + if (unlikely(next_offset == chunk_offset + chunk_size)) + invalidate_next_offset(); + else if (unlikely(next_offset == get_chunk_offset(slot) + + get_chunk_size(slot))) + invalidate_next_offset(); + // copy the chunk to the new slot + set_chunk_size(slot, num_bytes); + set_chunk_offset(slot, chunk_offset); + // remove from the freelist + if (i < total_count - 1) { + uint8_t *p = &m_data[kPayloadOffset + slot_size * i]; + memmove(p, p + slot_size, slot_size * (total_count - i - 1)); + } + set_freelist_count(get_freelist_count() - 1); + return (get_chunk_offset(slot)); + } + } + + ham_assert(!"shouldn't be here"); + throw Exception(HAM_INTERNAL_ERROR); + } + + // Returns true if |key| cannot be inserted because a split is required. + // Unlike implied by the name, this function will try to re-arrange the + // node in order for the key to fit in. + bool requires_split(size_t node_count, size_t required_size) { + return (!can_insert(node_count) + || !can_allocate_space(node_count, required_size)); + } + + // Verifies that there are no overlapping chunks + void check_integrity(size_t node_count) const { + typedef std::pair<uint32_t, uint32_t> Range; + //typedef std::vector<Range> RangeVec; + uint32_t total_count = node_count + get_freelist_count(); + + ham_assert(node_count > 1 + ? get_next_offset(node_count) > 0 + : true); + + if (total_count > get_capacity()) { + ham_trace(("integrity violated: total count %u (%u+%u) > capacity %u", + total_count, node_count, get_freelist_count(), + get_capacity())); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + + //RangeVec ranges; + //ranges.reserve(total_count); + uint32_t next_offset = 0; + for (uint32_t i = 0; i < total_count; i++) { + Range range = std::make_pair(get_chunk_offset(i), get_chunk_size(i)); + uint32_t next = range.first + range.second; + if (next >= next_offset) + next_offset = next; + //ranges.push_back(range); + } + +#if 0 + std::sort(ranges.begin(), ranges.end()); + + if (!ranges.empty()) { + for (uint32_t i = 0; i < ranges.size() - 1; i++) { + if (ranges[i].first + ranges[i].second > ranges[i + 1].first) { + ham_trace(("integrity violated: slot %u/%u overlaps with %lu", + ranges[i].first, ranges[i].second, + ranges[i + 1].first)); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + } +#endif + + if (next_offset != get_next_offset(node_count)) { + ham_trace(("integrity violated: next offset %d, cached offset %d", + next_offset, get_next_offset(node_count))); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + if (next_offset != calc_next_offset(node_count)) { + ham_trace(("integrity violated: next offset %d, calculated offset %d", + next_offset, calc_next_offset(node_count))); + throw Exception(HAM_INTEGRITY_VIOLATED); + } + } + + // Splits an index and moves all chunks starting from position |pivot| + // to the other index. + // The other index *must* be empty! + void split(UpfrontIndex *other, size_t node_count, int pivot) { + other->clear(); + + // now copy key by key + for (size_t i = pivot; i < node_count; i++) { + other->insert(i - pivot, i - pivot); + uint32_t size = get_chunk_size(i); + uint32_t offset = other->allocate_space(i - pivot, i - pivot, size); + memcpy(other->get_chunk_data_by_offset(offset), + get_chunk_data_by_offset(get_chunk_offset(i)), + size); + } + + // this node has lost lots of its data - make sure that it will be + // vacuumized as soon as more data is allocated + m_vacuumize_counter += node_count; + set_freelist_count(0); + set_next_offset((uint32_t)-1); + } + + // Merges all chunks from the |other| index to this index + void merge_from(UpfrontIndex *other, size_t node_count, + size_t other_node_count) { + vacuumize(node_count); + + for (size_t i = 0; i < other_node_count; i++) { + insert(i + node_count, i + node_count); + uint32_t size = other->get_chunk_size(i); + uint32_t offset = allocate_space(i + node_count, i + node_count, size); + memcpy(get_chunk_data_by_offset(offset), + other->get_chunk_data_by_offset(other->get_chunk_offset(i)), + size); + } + + other->clear(); + } + + // Returns a pointer to the actual data of a chunk + uint8_t *get_chunk_data_by_offset(uint32_t offset) { + return (&m_data[kPayloadOffset + + get_capacity() * get_full_index_size() + + offset]); + } + + // Returns a pointer to the actual data of a chunk + uint8_t *get_chunk_data_by_offset(uint32_t offset) const { + return (&m_data[kPayloadOffset + + get_capacity() * get_full_index_size() + + offset]); + } + + // Reduces the capacity of the UpfrontIndex, if required + void reduce_capacity(size_t node_count) { + size_t old_capacity = get_capacity(); + if (node_count > 0 && old_capacity > node_count + 4) { + size_t new_capacity = old_capacity - (old_capacity - node_count) / 2; + if (new_capacity != old_capacity) + change_range_size(node_count, m_data, m_range_size, new_capacity); + } + } + + // Re-arranges the node: moves all keys sequentially to the beginning + // of the key space, removes the whole freelist. + // + // This call is extremely expensive! Try to avoid it as much as possible. + void vacuumize(size_t node_count) { + if (m_vacuumize_counter < 10) { + if (get_freelist_count() > 0) { + set_freelist_count(0); + invalidate_next_offset(); + } + return; + } + + // get rid of the freelist - this node is now completely rewritten, + // and the freelist would just complicate things + set_freelist_count(0); + + // make a copy of all indices (excluding the freelist) + bool requires_sort = false; + SortHelper *s = (SortHelper *)::alloca(node_count * sizeof(SortHelper)); + for (size_t i = 0; i < node_count; i++) { + s[i].slot = i; + s[i].offset = get_chunk_offset(i); + if (i > 0 && s[i].offset < s[i - 1].offset) + requires_sort = true; + } + + // sort them by offset; this is a very expensive call. only sort if + // it's absolutely necessary! + if (requires_sort) + std::sort(&s[0], &s[node_count], sort_by_offset); + + // shift all keys to the left, get rid of all gaps at the front of the + // key data or between the keys + uint32_t next_offset = 0; + uint32_t start = kPayloadOffset + get_capacity() * get_full_index_size(); + for (size_t i = 0; i < node_count; i++) { + uint32_t offset = s[i].offset; + int slot = s[i].slot; + uint32_t size = get_chunk_size(slot); + if (offset != next_offset) { + // shift key to the left + memmove(&m_data[start + next_offset], + get_chunk_data_by_offset(offset), size); + // store the new offset + set_chunk_offset(slot, next_offset); + } + next_offset += size; + } + + set_next_offset(next_offset); + m_vacuumize_counter = 0; + } + + // Invalidates the cached "next offset". In some cases it's necessary + // that the caller forces a re-evaluation of the next offset. Although + // i *think* that this method could become private, but the effort + // is not worth the gain. + void invalidate_next_offset() { + set_next_offset((uint32_t)-1); + } + + // Same as above, but only if the next_offset equals |new_offset| + void maybe_invalidate_next_offset(size_t new_offset) { + if (get_next_offset(0) == new_offset) + invalidate_next_offset(); + } + + // Returns the capacity + size_t get_capacity() const { + return (*(uint32_t *)(m_data + 8)); + } + + // Returns the offset of the unused space at the end of the page + uint32_t get_next_offset(size_t node_count) { + uint32_t ret = *(uint32_t *)(m_data + 4); + if (unlikely(ret == (uint32_t)-1 && node_count > 0)) { + ret = calc_next_offset(node_count); + set_next_offset(ret); + } + return (ret); + } + + private: + friend class UpfrontIndexFixture; + + // Resets the page + void clear() { + set_freelist_count(0); + set_next_offset(0); + m_vacuumize_counter = 0; + } + + // Returns the offset of the unused space at the end of the page + // (const version) + uint32_t get_next_offset(size_t node_count) const { + uint32_t ret = *(uint32_t *)(m_data + 4); + if (unlikely(ret == (uint32_t)-1)) + return (calc_next_offset(node_count)); + return (ret); + } + + // Returns the size (in bytes) where payload data can be stored + size_t get_usable_data_size() const { + return (m_range_size - kPayloadOffset + - get_capacity() * get_full_index_size()); + } + + // Sets the chunk offset of a slot + void set_chunk_offset(int slot, uint32_t offset) { + uint8_t *p = &m_data[kPayloadOffset + get_full_index_size() * slot]; + if (m_sizeof_offset == 2) + *(uint16_t *)p = (uint16_t)offset; + else + *(uint32_t *)p = offset; + } + + // Returns the number of freelist entries + size_t get_freelist_count() const { + return (*(uint32_t *)m_data); + } + + // Sets the number of freelist entries + void set_freelist_count(size_t freelist_count) { + ham_assert(freelist_count <= get_capacity()); + *(uint32_t *)m_data = freelist_count; + } + + // Calculates and returns the next offset; does not store it + uint32_t calc_next_offset(size_t node_count) const { + uint32_t total_count = node_count + get_freelist_count(); + uint32_t next_offset = 0; + for (uint32_t i = 0; i < total_count; i++) { + uint32_t next = get_chunk_offset(i) + get_chunk_size(i); + if (next >= next_offset) + next_offset = next; + } + return (next_offset); + } + + // Sets the offset of the unused space at the end of the page + void set_next_offset(uint32_t next_offset) { + *(uint32_t *)(m_data + 4) = next_offset; + } + + // Sets the capacity (number of slots) + void set_capacity(size_t capacity) { + ham_assert(capacity > 0); + *(uint32_t *)(m_data + 8) = (uint32_t)capacity; + } + + // The physical data in the node + uint8_t *m_data; + + // The size of the offset; either 16 or 32 bits, depending on page size + size_t m_sizeof_offset; + + // The size of the range, in bytes + size_t m_range_size; + + // A counter to indicate when rearranging the data makes sense + int m_vacuumize_counter; +}; + +} // namespace DefLayout + +} // namespace hamsterdb + +#endif /* HAM_BTREE_UPFRONT_INDEX_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3cache/cache.h b/plugins/Dbx_kv/src/hamsterdb/src/3cache/cache.h new file mode 100644 index 0000000000..a24daf3828 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3cache/cache.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The Cache Manager + * + * Stores pages in a non-intrusive hash table (each Page instance keeps + * next/previous pointers for the overflow bucket). Can efficiently purge + * unused pages, because all pages are also stored in a (non-intrusive) + * linked list, and whenever a page is accessed it is removed and re-inserted + * at the head. The tail therefore points to the page which was not used + * in a long time, and is the primary candidate for purging. + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_CACHE_H +#define HAM_CACHE_H + +#include "0root/root.h" + +#include <vector> + +#include "ham/hamsterdb_int.h" + +// Always verify that a file of level N does not include headers > N! +#include "2page/page.h" +#include "2page/page_collection.h" +#include "2config/env_config.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Cache +{ + enum { + // The number of buckets should be a prime number or similar, as it + // is used in a MODULO hash scheme + kBucketSize = 10317, + }; + + template<typename Purger> + struct PurgeIfSelector + { + PurgeIfSelector(Cache *cache, Purger &purger) + : m_cache(cache), m_purger(purger) { + } + + bool operator()(Page *page) { + if (m_purger(page)) { + m_cache->del(page); + delete page; + } + // don't remove page from list; it was already removed above + return (false); + } + + Cache *m_cache; + Purger &m_purger; + }; + + public: + // The default constructor + Cache(const EnvironmentConfiguration &config) + : m_capacity_bytes(config.flags & HAM_CACHE_UNLIMITED + ? 0xffffffffffffffffull + : config.cache_size_bytes), + m_page_size_bytes(config.page_size_bytes), + m_alloc_elements(0), m_totallist(Page::kListCache), + m_buckets(kBucketSize, PageCollection(Page::kListBucket)), + m_cache_hits(0), m_cache_misses(0) { + ham_assert(m_capacity_bytes > 0); + } + + // Fills in the current metrics + void fill_metrics(ham_env_metrics_t *metrics) const { + metrics->cache_hits = m_cache_hits; + metrics->cache_misses = m_cache_misses; + } + + // Retrieves a page from the cache, also removes the page from the cache + // and re-inserts it at the front. Returns null if the page was not cached. + Page *get(uint64_t address) { + size_t hash = calc_hash(address); + + Page *page = m_buckets[hash].get(address);; + if (!page) { + m_cache_misses++; + return (0); + } + + // Now re-insert the page at the head of the "totallist", and + // thus move far away from the tail. The pages at the tail are highest + // candidates to be deleted when the cache is purged. + m_totallist.del(page); + m_totallist.put(page); + m_cache_hits++; + return (page); + } + + // Stores a page in the cache + void put(Page *page) { + size_t hash = calc_hash(page->get_address()); + ham_assert(page->get_data()); + + /* First remove the page from the cache, if it's already cached + * + * Then re-insert the page at the head of the list. The tail will + * point to the least recently used page. + */ + m_totallist.del(page); + m_totallist.put(page); + + if (page->is_allocated()) + m_alloc_elements++; + m_buckets[hash].put(page); + } + + // Removes a page from the cache + void del(Page *page) { + ham_assert(page->get_address() != 0); + size_t hash = calc_hash(page->get_address()); + /* remove the page from the cache buckets */ + m_buckets[hash].del(page); + + /* remove it from the list of all cached pages */ + if (m_totallist.del(page) && page->is_allocated()) + m_alloc_elements--; + } + + // Purges the cache. Implements a LRU eviction algorithm. Dirty pages are + // forwarded to the |processor()| for flushing. + // + // Tries to purge at least 20 pages. In benchmarks this has proven to + // be a good limit. + template<typename Processor> + void purge(Processor &processor, Page *ignore_page) { + int limit = int(current_elements() + - (m_capacity_bytes / m_page_size_bytes)); + + Page *page = m_totallist.tail(); + for (int i = 0; i < limit && page != 0; i++) { + Page *next = page->get_previous(Page::kListCache); + + // dirty pages are flushed by the worker thread + if (page->is_dirty()) { + processor(page); + page = next; + continue; + } + // non-dirty pages are deleted if possible + if (!page->is_dirty() + && page->cursor_list() == 0 + && page != ignore_page + && page->mutex().try_lock()) { + del(page); + page->mutex().unlock(); + delete page; + } + + page = next; + } + } + + // Visits all pages in the "totallist". If |cb| returns true then the + // page is removed and deleted. This is used by the Environment + // to flush (and delete) pages. + template<typename Purger> + void purge_if(Purger &purger) { + PurgeIfSelector<Purger> selector(this, purger); + m_totallist.extract(selector); + } + + // Returns true if the capacity limits are exceeded + bool is_cache_full() const { + return (current_elements() * m_page_size_bytes + > m_capacity_bytes); + } + + // Returns the capacity (in bytes) + uint64_t capacity() const { + return (m_capacity_bytes); + } + + // Returns the number of currently cached elements + size_t current_elements() const { + return (m_totallist.size()); + } + + // Returns the number of currently cached elements (excluding those that + // are mmapped) + size_t allocated_elements() const { + return (m_alloc_elements); + } + + private: + // Calculates the hash of a page address + size_t calc_hash(uint64_t value) const { + return ((size_t)(value % Cache::kBucketSize)); + } + + // the capacity (in bytes) + uint64_t m_capacity_bytes; + + // the current page size (in bytes) + uint64_t m_page_size_bytes; + + // the current number of cached elements that were allocated (and not + // mapped) + size_t m_alloc_elements; + + // linked list of ALL cached pages + PageCollection m_totallist; + + // The hash table buckets - each is a linked list of Page pointers + std::vector<PageCollection> m_buckets; + + // counts the cache hits + uint64_t m_cache_hits; + + // counts the cache misses + uint64_t m_cache_misses; +}; + +} // namespace hamsterdb + +#endif /* HAM_CACHE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.cc b/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.cc new file mode 100644 index 0000000000..2e5ace06f5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.cc @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Always verify that a file of level N does not include headers > N! +#include "1errorinducer/errorinducer.h" +#include "2device/device.h" +#include "2page/page.h" +#include "3changeset/changeset.h" +#include "3journal/journal.h" +#include "3page_manager/page_manager.h" +#include "4db/db.h" +#include "4env/env_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* a unittest hook for Changeset::flush() */ +void (*g_CHANGESET_POST_LOG_HOOK)(void); + +struct PageCollectionVisitor +{ + PageCollectionVisitor(Page **pages) + : num_pages(0), pages(pages) { + } + + void prepare(size_t size) { + } + + bool operator()(Page *page) { + if (page->is_dirty() == true) { + pages[num_pages] = page; + ++num_pages; + } + // |page| is now removed from the Changeset + page->mutex().unlock(); + return (true); + } + + int num_pages; + Page **pages; +}; + +void +Changeset::flush(uint64_t lsn) +{ + // now flush all modified pages to disk + if (m_collection.is_empty()) + return; + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + + // Fetch the pages, ignoring all pages that are not dirty + Page **pages = (Page **)::alloca(sizeof(Page *) * m_collection.size()); + PageCollectionVisitor visitor(pages); + m_collection.extract(visitor); + + // TODO sort by address (really?) + + if (visitor.num_pages == 0) + return; + + // If only one page is modified then the modification is atomic. The page + // is written to the btree (no log required). + // + // If more than one page is modified then the modification is no longer + // atomic. All dirty pages are written to the log. + if (visitor.num_pages > 1) { + m_env->journal()->append_changeset((const Page **)visitor.pages, + visitor.num_pages, lsn); + } + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + + /* execute a post-log hook; this hook is set by the unittest framework + * and can be used to make a backup copy of the logfile */ + if (g_CHANGESET_POST_LOG_HOOK) + g_CHANGESET_POST_LOG_HOOK(); + + /* now write all the pages to the file; if any of these writes fail, + * we can still recover from the log */ + for (int i = 0; i < visitor.num_pages; i++) { + Page *p = visitor.pages[i]; + if (p->is_without_header() == false) + p->set_lsn(lsn); + p->flush(); + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + } + + /* flush the file handle (if required) */ + if (m_env->get_flags() & HAM_ENABLE_FSYNC) + m_env->device()->flush(); + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.h b/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.h new file mode 100644 index 0000000000..a21c6f45f9 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3changeset/changeset.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A changeset collects all pages that are modified during a single + * operation. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_CHANGESET_H +#define HAM_CHANGESET_H + +#include "0root/root.h" + +#include <stdlib.h> + +// Always verify that a file of level N does not include headers > N! +#include "2page/page.h" +#include "2page/page_collection.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class LocalEnvironment; + +class Changeset +{ + struct UnlockPage + { + bool operator()(Page *page) { + #ifdef HAM_ENABLE_HELGRIND + page->mutex().try_lock(); + #endif + page->mutex().unlock(); + return (true); + } + }; + + public: + Changeset(LocalEnvironment *env) + : m_env(env), m_collection(Page::kListChangeset) { + } + + /* + * Returns a page from the changeset, or NULL if the page is not part + * of the changeset + */ + Page *get(uint64_t address) { + return (m_collection.get(address)); + } + + /* Append a new page to the changeset. The page is locked. */ + void put(Page *page) { + if (!has(page)) { + page->mutex().lock(); + } + m_collection.put(page); + } + + /* Removes a page from the changeset. The page is unlocked. */ + void del(Page *page) { + page->mutex().unlock(); + m_collection.del(page); + } + + /* Check if the page is already part of the changeset */ + bool has(Page *page) const { + return (m_collection.has(page)); + } + + /* Returns true if the changeset is empty */ + bool is_empty() const { + return (m_collection.is_empty()); + } + + /* Removes all pages from the changeset. The pages are unlocked. */ + void clear() { + UnlockPage unlocker; + m_collection.for_each(unlocker); + m_collection.clear(); + } + + /* + * Flush all pages in the changeset - first write them to the log, then + * write them to the disk. + * On success: will clear the changeset and the journal + */ + void flush(uint64_t lsn); + + private: + /* The Environment */ + LocalEnvironment *m_env; + + /* The pages which were added to this Changeset */ + PageCollection m_collection; +}; + +} // namespace hamsterdb + +#endif /* HAM_CHANGESET_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.cc b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.cc new file mode 100644 index 0000000000..50e749240f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.cc @@ -0,0 +1,862 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> +#ifndef HAM_OS_WIN32 +# include <libgen.h> +#endif + +#include "1base/error.h" +#include "1errorinducer/errorinducer.h" +#include "1os/os.h" +#include "2device/device.h" +#include "3journal/journal.h" +#include "3page_manager/page_manager.h" +#include "4db/db.h" +#include "4txn/txn_local.h" +#include "4env/env_local.h" +#include "4context/context.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +Journal::Journal(LocalEnvironment *env) + : m_state(env) +{ +} + +void +Journal::create() +{ + // create the two files + for (int i = 0; i < 2; i++) { + std::string path = get_path(i); + m_state.files[i].create(path.c_str(), 0644); + } +} + +void +Journal::open() +{ + // open the two files + try { + std::string path = get_path(0); + m_state.files[0].open(path.c_str(), false); + path = get_path(1); + m_state.files[1].open(path.c_str(), 0); + } + catch (Exception &ex) { + m_state.files[1].close(); + m_state.files[0].close(); + throw ex; + } +} + +int +Journal::switch_files_maybe() +{ + int other = m_state.current_fd ? 0 : 1; + + // determine the journal file which is used for this transaction + // if the "current" file is not yet full, continue to write to this file + if (m_state.open_txn[m_state.current_fd] + + m_state.closed_txn[m_state.current_fd] + < m_state.threshold) + return (m_state.current_fd); + + // If the other file does no longer have open Transactions then + // delete the other file and use the other file as the current file + if (m_state.open_txn[other] == 0) { + clear_file(other); + m_state.current_fd = other; + // fall through + } + + // Otherwise just continue using the current file + return (m_state.current_fd); +} + +void +Journal::append_txn_begin(LocalTransaction *txn, const char *name, uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + ham_assert((txn->get_flags() & HAM_TXN_TEMPORARY) == 0); + + PJournalEntry entry; + entry.txn_id = txn->get_id(); + entry.type = kEntryTypeTxnBegin; + entry.lsn = lsn; + if (name) + entry.followup_size = strlen(name) + 1; + + txn->set_log_desc(switch_files_maybe()); + + int cur = txn->get_log_desc(); + + if (txn->get_name().size()) + append_entry(cur, (uint8_t *)&entry, (uint32_t)sizeof(entry), + (uint8_t *)txn->get_name().c_str(), + (uint32_t)txn->get_name().size() + 1); + else + append_entry(cur, (uint8_t *)&entry, (uint32_t)sizeof(entry)); + maybe_flush_buffer(cur); + + m_state.open_txn[cur]++; + + // store the fp-index in the journal structure; it's needed for + // journal_append_checkpoint() to quickly find out which file is + // the newest + m_state.current_fd = cur; +} + +void +Journal::append_txn_abort(LocalTransaction *txn, uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + ham_assert((txn->get_flags() & HAM_TXN_TEMPORARY) == 0); + + int idx; + PJournalEntry entry; + entry.lsn = lsn; + entry.txn_id = txn->get_id(); + entry.type = kEntryTypeTxnAbort; + + // update the transaction counters of this logfile + idx = txn->get_log_desc(); + m_state.open_txn[idx]--; + m_state.closed_txn[idx]++; + + append_entry(idx, (uint8_t *)&entry, sizeof(entry)); + maybe_flush_buffer(idx); + // no need for fsync - incomplete transactions will be aborted anyway +} + +void +Journal::append_txn_commit(LocalTransaction *txn, uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + ham_assert((txn->get_flags() & HAM_TXN_TEMPORARY) == 0); + + PJournalEntry entry; + entry.lsn = lsn; + entry.txn_id = txn->get_id(); + entry.type = kEntryTypeTxnCommit; + + // do not yet update the transaction counters of this logfile; just + // because the txn was committed does not mean that it will be flushed + // immediately. The counters will be modified in transaction_flushed(). + int idx = txn->get_log_desc(); + + append_entry(idx, (uint8_t *)&entry, sizeof(entry)); + + // and flush the file + flush_buffer(idx, m_state.env->get_flags() & HAM_ENABLE_FSYNC); +} + +void +Journal::append_insert(Database *db, LocalTransaction *txn, + ham_key_t *key, ham_record_t *record, uint32_t flags, + uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + PJournalEntry entry; + PJournalEntryInsert insert; + uint32_t size = sizeof(PJournalEntryInsert) + + key->size + + (flags & HAM_PARTIAL + ? record->partial_size + : record->size) + - 1; + + entry.lsn = lsn; + entry.dbname = db->name(); + entry.type = kEntryTypeInsert; + entry.followup_size = size; + + int idx; + if (txn->get_flags() & HAM_TXN_TEMPORARY) { + entry.txn_id = 0; + idx = switch_files_maybe(); + m_state.closed_txn[idx]++; + } + else { + entry.txn_id = txn->get_id(); + idx = txn->get_log_desc(); + } + + insert.key_size = key->size; + insert.record_size = record->size; + insert.record_partial_size = record->partial_size; + insert.record_partial_offset = record->partial_offset; + insert.insert_flags = flags; + + // append the entry to the logfile + append_entry(idx, (uint8_t *)&entry, sizeof(entry), + (uint8_t *)&insert, sizeof(PJournalEntryInsert) - 1, + (uint8_t *)key->data, key->size, + (uint8_t *)record->data, (flags & HAM_PARTIAL + ? record->partial_size + : record->size)); + maybe_flush_buffer(idx); +} + +void +Journal::append_erase(Database *db, LocalTransaction *txn, ham_key_t *key, + int duplicate_index, uint32_t flags, uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + PJournalEntry entry; + PJournalEntryErase erase; + uint32_t size = sizeof(PJournalEntryErase) + key->size - 1; + + entry.lsn = lsn; + entry.dbname = db->name(); + entry.type = kEntryTypeErase; + entry.followup_size = size; + erase.key_size = key->size; + erase.erase_flags = flags; + erase.duplicate = duplicate_index; + + int idx; + if (txn->get_flags() & HAM_TXN_TEMPORARY) { + entry.txn_id = 0; + idx = switch_files_maybe(); + m_state.closed_txn[idx]++; + } + else { + entry.txn_id = txn->get_id(); + idx = txn->get_log_desc(); + } + + // append the entry to the logfile + append_entry(idx, (uint8_t *)&entry, sizeof(entry), + (uint8_t *)&erase, sizeof(PJournalEntryErase) - 1, + (uint8_t *)key->data, key->size); + maybe_flush_buffer(idx); +} + +void +Journal::append_changeset(const Page **pages, int num_pages, uint64_t lsn) +{ + if (m_state.disable_logging) + return; + + PJournalEntry entry; + PJournalEntryChangeset changeset; + + entry.lsn = lsn; + entry.dbname = 0; + entry.txn_id = 0; + entry.type = kEntryTypeChangeset; + // followup_size is incomplete - the actual page sizes are added later + entry.followup_size = sizeof(PJournalEntryChangeset); + changeset.num_pages = num_pages; + + // we need the current position in the file buffer. if compression is enabled + // then we do not know the actual followup-size of this entry. it will be + // patched in later. + uint32_t entry_position = m_state.buffer[m_state.current_fd].get_size(); + + // write the data to the file + append_entry(m_state.current_fd, (uint8_t *)&entry, sizeof(entry), + (uint8_t *)&changeset, sizeof(PJournalEntryChangeset)); + + size_t page_size = m_state.env->config().page_size_bytes; + for (int i = 0; i < num_pages; i++) { + entry.followup_size += append_changeset_page(pages[i], page_size); + } + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + + // and patch in the followup-size + m_state.buffer[m_state.current_fd].overwrite(entry_position, + (uint8_t *)&entry, sizeof(entry)); + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + + // and flush the file + flush_buffer(m_state.current_fd, m_state.env->get_flags() & HAM_ENABLE_FSYNC); + + HAM_INDUCE_ERROR(ErrorInducer::kChangesetFlush); + + // if recovery is enabled (w/o transactions) then simulate a "commit" to + // make sure that the log files are switched properly + m_state.closed_txn[m_state.current_fd]++; + (void)switch_files_maybe(); +} + +uint32_t +Journal::append_changeset_page(const Page *page, uint32_t page_size) +{ + PJournalEntryPageHeader header(page->get_address()); + + append_entry(m_state.current_fd, (uint8_t *)&header, sizeof(header), + page->get_raw_payload(), page_size); + return (page_size + sizeof(header)); +} + +void +Journal::transaction_flushed(LocalTransaction *txn) +{ + ham_assert((txn->get_flags() & HAM_TXN_TEMPORARY) == 0); + if (m_state.disable_logging) // ignore this call during recovery + return; + + int idx = txn->get_log_desc(); + ham_assert(m_state.open_txn[idx] > 0); + m_state.open_txn[idx]--; + m_state.closed_txn[idx]++; +} + +void +Journal::get_entry(Iterator *iter, PJournalEntry *entry, ByteArray *auxbuffer) +{ + uint64_t filesize; + + auxbuffer->clear(); + + // if iter->offset is 0, then the iterator was created from scratch + // and we start reading from the first (oldest) entry. + // + // The oldest of the two logfiles is always the "other" one (the one + // NOT in current_fd). + if (iter->offset == 0) { + iter->fdstart = iter->fdidx = + m_state.current_fd == 0 + ? 1 + : 0; + } + + // get the size of the journal file + filesize = m_state.files[iter->fdidx].get_file_size(); + + // reached EOF? then either skip to the next file or we're done + if (filesize == iter->offset) { + if (iter->fdstart == iter->fdidx) { + iter->fdidx = iter->fdidx == 1 ? 0 : 1; + iter->offset = 0; + filesize = m_state.files[iter->fdidx].get_file_size(); + } + else { + entry->lsn = 0; + return; + } + } + + // second file is also empty? then return + if (filesize == iter->offset) { + entry->lsn = 0; + return; + } + + // now try to read the next entry + try { + m_state.files[iter->fdidx].pread(iter->offset, entry, sizeof(*entry)); + + iter->offset += sizeof(*entry); + + // read auxiliary data if it's available + if (entry->followup_size) { + auxbuffer->resize((uint32_t)entry->followup_size); + + m_state.files[iter->fdidx].pread(iter->offset, auxbuffer->get_ptr(), + (size_t)entry->followup_size); + iter->offset += entry->followup_size; + } + } + catch (Exception &) { + ham_trace(("failed to read journal entry, aborting recovery")); + entry->lsn = 0; // this triggers the end of recovery + } +} + +void +Journal::close(bool noclear) +{ + int i; + + // the noclear flag is set during testing, for checking whether the files + // contain the correct data. Flush the buffers, otherwise the tests will + // fail because data is missing + if (noclear) { + flush_buffer(0); + flush_buffer(1); + } + + if (!noclear) + clear(); + + for (i = 0; i < 2; i++) { + m_state.files[i].close(); + m_state.buffer[i].clear(); + } +} + +Database * +Journal::get_db(uint16_t dbname) +{ + // first check if the Database is already open + JournalState::DatabaseMap::iterator it = m_state.database_map.find(dbname); + if (it != m_state.database_map.end()) + return (it->second); + + // not found - open it + Database *db = 0; + DatabaseConfiguration config; + config.db_name = dbname; + ham_status_t st = m_state.env->open_db(&db, config, 0); + if (st) + throw Exception(st); + m_state.database_map[dbname] = db; + return (db); +} + +Transaction * +Journal::get_txn(LocalTransactionManager *txn_manager, uint64_t txn_id) +{ + Transaction *txn = txn_manager->get_oldest_txn(); + while (txn) { + if (txn->get_id() == txn_id) + return (txn); + txn = txn->get_next(); + } + + return (0); +} + +void +Journal::close_all_databases() +{ + ham_status_t st = 0; + + JournalState::DatabaseMap::iterator it = m_state.database_map.begin(); + while (it != m_state.database_map.end()) { + JournalState::DatabaseMap::iterator it2 = it; it++; + st = ham_db_close((ham_db_t *)it2->second, HAM_DONT_LOCK); + if (st) { + ham_log(("ham_db_close() failed w/ error %d (%s)", st, ham_strerror(st))); + throw Exception(st); + } + } + m_state.database_map.clear(); +} + +void +Journal::abort_uncommitted_txns(LocalTransactionManager *txn_manager) +{ + Transaction *txn = txn_manager->get_oldest_txn(); + + while (txn) { + if (!txn->is_committed()) + txn->abort(); + txn = txn->get_next(); + } +} + +void +Journal::recover(LocalTransactionManager *txn_manager) +{ + Context context(m_state.env, 0, 0); + + // first re-apply the last changeset + uint64_t start_lsn = recover_changeset(); + + // load the state of the PageManager; the PageManager state is loaded AFTER + // physical recovery because its page might have been restored in + // recover_changeset() + uint64_t page_manager_blobid = m_state.env->header()->get_page_manager_blobid(); + if (page_manager_blobid != 0) { + m_state.env->page_manager()->initialize(page_manager_blobid); + } + + // then start the normal recovery + if (m_state.env->get_flags() & HAM_ENABLE_TRANSACTIONS) + recover_journal(&context, txn_manager, start_lsn); +} + +uint64_t +Journal::scan_for_newest_changeset(File *file, uint64_t *position) +{ + Iterator it; + PJournalEntry entry; + ByteArray buffer; + uint64_t result = 0; + + // get the next entry + try { + uint64_t filesize = file->get_file_size(); + + while (it.offset < filesize) { + file->pread(it.offset, &entry, sizeof(entry)); + + if (entry.lsn == 0) + break; + + if (entry.type == kEntryTypeChangeset) { + *position = it.offset; + result = entry.lsn; + } + + // increment the offset + it.offset += sizeof(entry); + if (entry.followup_size) + it.offset += entry.followup_size; + } + } + catch (Exception &ex) { + ham_log(("exception (error %d) while reading journal", ex.code)); + } + + return (result); +} + +uint64_t +Journal::recover_changeset() +{ + // scan through both files, look for the file with the newest changeset + uint64_t position0, position1, position; + uint64_t lsn1 = scan_for_newest_changeset(&m_state.files[0], &position0); + uint64_t lsn2 = scan_for_newest_changeset(&m_state.files[1], &position1); + + // both files are empty or do not contain a changeset? + if (lsn1 == 0 && lsn2 == 0) + return (0); + + // re-apply the newest changeset + m_state.current_fd = lsn1 > lsn2 ? 0 : 1; + position = lsn1 > lsn2 ? position0 : position1; + + PJournalEntry entry; + uint64_t start_lsn = 0; + + try { + m_state.files[m_state.current_fd].pread(position, &entry, sizeof(entry)); + position += sizeof(entry); + ham_assert(entry.type == kEntryTypeChangeset); + + // Read the Changeset header + PJournalEntryChangeset changeset; + m_state.files[m_state.current_fd].pread(position, &changeset, + sizeof(changeset)); + position += sizeof(changeset); + + uint32_t page_size = m_state.env->config().page_size_bytes; + ByteArray arena(page_size); + + uint64_t file_size = m_state.env->device()->file_size(); + + // for each page in this changeset... + for (uint32_t i = 0; i < changeset.num_pages; i++) { + PJournalEntryPageHeader page_header; + m_state.files[m_state.current_fd].pread(position, &page_header, + sizeof(page_header)); + position += sizeof(page_header); + m_state.files[m_state.current_fd].pread(position, arena.get_ptr(), + page_size); + position += page_size; + + Page *page; + + // now write the page to disk + if (page_header.address == file_size) { + file_size += page_size; + + page = new Page(m_state.env->device()); + page->alloc(0); + } + else if (page_header.address > file_size) { + file_size = (size_t)page_header.address + page_size; + m_state.env->device()->truncate(file_size); + + page = new Page(m_state.env->device()); + page->fetch(page_header.address); + } + else { + page = new Page(m_state.env->device()); + page->fetch(page_header.address); + } + + // only overwrite the page data if the page's last modification + // is OLDER than the changeset! + bool skip = false; + if (page->is_without_header() == false) { + if (page->get_lsn() > entry.lsn) { + skip = true; + start_lsn = page->get_lsn(); + } + } + + if (!skip) { + // overwrite the page data + memcpy(page->get_data(), arena.get_ptr(), page_size); + + ham_assert(page->get_address() == page_header.address); + + // flush the modified page to disk + page->set_dirty(true); + page->flush(); + } + + delete page; + } + } + catch (Exception &) { + ham_trace(("Exception when applying changeset; skipping changeset")); + // fall through + } + + return (std::max(start_lsn, entry.lsn)); +} + +void +Journal::recover_journal(Context *context, + LocalTransactionManager *txn_manager, uint64_t start_lsn) +{ + ham_status_t st = 0; + Iterator it; + ByteArray buffer; + + /* recovering the journal is rather simple - we iterate over the + * files and re-apply EVERY operation (incl. txn_begin and txn_abort), + * that was not yet flushed with a Changeset. + * + * Basically we iterate over both log files and skip everything with + * a sequence number (lsn) smaller the one of the last Changeset. + * + * When done then auto-abort all transactions that were not yet + * committed. + */ + + // make sure that there are no pending transactions - start with + // a clean state! + ham_assert(txn_manager->get_oldest_txn() == 0); + ham_assert(m_state.env->get_flags() & HAM_ENABLE_TRANSACTIONS); + ham_assert(m_state.env->get_flags() & HAM_ENABLE_RECOVERY); + + // do not append to the journal during recovery + m_state.disable_logging = true; + + do { + PJournalEntry entry; + + // get the next entry + get_entry(&it, &entry, &buffer); + + // reached end of logfile? + if (!entry.lsn) + break; + + // re-apply this operation + switch (entry.type) { + case kEntryTypeTxnBegin: { + Transaction *txn = 0; + st = ham_txn_begin((ham_txn_t **)&txn, (ham_env_t *)m_state.env, + (const char *)buffer.get_ptr(), 0, HAM_DONT_LOCK); + // on success: patch the txn ID + if (st == 0) { + txn->set_id(entry.txn_id); + txn_manager->set_txn_id(entry.txn_id); + } + break; + } + case kEntryTypeTxnAbort: { + Transaction *txn = get_txn(txn_manager, entry.txn_id); + st = ham_txn_abort((ham_txn_t *)txn, HAM_DONT_LOCK); + break; + } + case kEntryTypeTxnCommit: { + Transaction *txn = get_txn(txn_manager, entry.txn_id); + st = ham_txn_commit((ham_txn_t *)txn, HAM_DONT_LOCK); + break; + } + case kEntryTypeInsert: { + PJournalEntryInsert *ins = (PJournalEntryInsert *)buffer.get_ptr(); + Transaction *txn = 0; + Database *db; + ham_key_t key = {0}; + ham_record_t record = {0}; + if (!ins) { + st = HAM_IO_ERROR; + goto bail; + } + + // do not insert if the key was already flushed to disk + if (entry.lsn <= start_lsn) + continue; + + key.data = ins->get_key_data(); + key.size = ins->key_size; + record.data = ins->get_record_data(); + record.size = ins->record_size; + record.partial_size = ins->record_partial_size; + record.partial_offset = ins->record_partial_offset; + if (entry.txn_id) + txn = get_txn(txn_manager, entry.txn_id); + db = get_db(entry.dbname); + st = ham_db_insert((ham_db_t *)db, (ham_txn_t *)txn, + &key, &record, ins->insert_flags | HAM_DONT_LOCK); + break; + } + case kEntryTypeErase: { + PJournalEntryErase *e = (PJournalEntryErase *)buffer.get_ptr(); + Transaction *txn = 0; + Database *db; + ham_key_t key = {0}; + if (!e) { + st = HAM_IO_ERROR; + goto bail; + } + + // do not erase if the key was already erased from disk + if (entry.lsn <= start_lsn) + continue; + + if (entry.txn_id) + txn = get_txn(txn_manager, entry.txn_id); + db = get_db(entry.dbname); + key.data = e->get_key_data(); + key.size = e->key_size; + st = ham_db_erase((ham_db_t *)db, (ham_txn_t *)txn, &key, + e->erase_flags | HAM_DONT_LOCK); + // key might have already been erased when the changeset + // was flushed + if (st == HAM_KEY_NOT_FOUND) + st = 0; + break; + } + case kEntryTypeChangeset: { + // skip this; the changeset was already applied + break; + } + default: + ham_log(("invalid journal entry type or journal is corrupt")); + st = HAM_IO_ERROR; + } + + if (st) + goto bail; + } while (1); + +bail: + // all transactions which are not yet committed will be aborted + abort_uncommitted_txns(txn_manager); + + // also close and delete all open databases - they were created in get_db() + close_all_databases(); + + // flush all committed transactions + if (st == 0) + st = m_state.env->flush(HAM_FLUSH_COMMITTED_TRANSACTIONS); + + // re-enable the logging + m_state.disable_logging = false; + + if (st) + throw Exception(st); + + // clear the journal files + clear(); +} + +void +Journal::clear_file(int idx) +{ + if (m_state.files[idx].is_open()) { + m_state.files[idx].truncate(0); + + // after truncate, the file pointer is far beyond the new end of file; + // reset the file pointer, or the next write will resize the file to + // the original size + m_state.files[idx].seek(0, File::kSeekSet); + } + + // clear the transaction counters + m_state.open_txn[idx] = 0; + m_state.closed_txn[idx] = 0; + + // also clear the buffer with the outstanding data + m_state.buffer[idx].clear(); +} + +std::string +Journal::get_path(int i) +{ + std::string path; + + if (m_state.env->config().log_filename.empty()) { + path = m_state.env->config().filename; + } + else { + path = m_state.env->config().log_filename; +#ifdef HAM_OS_WIN32 + path += "\\"; + char fname[_MAX_FNAME]; + char ext[_MAX_EXT]; + _splitpath(m_state.env->config().filename.c_str(), 0, 0, fname, ext); + path += fname; + path += ext; +#else + path += "/"; + path += ::basename((char *)m_state.env->config().filename.c_str()); +#endif + } + if (i == 0) + path += ".jrn0"; + else if (i == 1) + path += ".jrn1"; + else + ham_assert(!"invalid index"); + return (path); +} + +JournalTest +Journal::test() +{ + return (JournalTest(&m_state)); +} + +JournalState::JournalState(LocalEnvironment *env) + : env(env), current_fd(0), threshold(env->config().journal_switch_threshold), + disable_logging(false), count_bytes_flushed(0), + count_bytes_before_compression(0), count_bytes_after_compression(0) +{ + if (threshold == 0) + threshold = kSwitchTxnThreshold; + + open_txn[0] = 0; + open_txn[1] = 0; + closed_txn[0] = 0; + closed_txn[1] = 0; +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.h b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.h new file mode 100644 index 0000000000..dd55b66fea --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal.h @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Routines for the journal - writing, reading, recovering + * + * The journal is a facility for storing logical and physical redo-information. + * + * The logical information describes the database operation (i.e. insert/erase), + * the physical information describes the modified pages. + * + * "Undo" information is not required because aborted Transactions are never + * written to disk. The journal only can "redo" operations. + * + * The journal is organized in two files. If one of the files grows too large + * then all new Transactions are stored in the other file + * ("Log file switching"). When all Transactions from file #0 are committed, + * and file #1 exceeds a limit, then the files are switched back again. + * + * For writing, files are buffered. The buffers are flushed when they + * exceed a certain threshold, when a Transaction is committed or a Changeset + * was written. In case of a commit or a changeset there will also be an + * fsync, if HAM_ENABLE_FSYNC is enabled. + * + * The physical information is a collection of pages which are modified in + * one or more database operations (i.e. ham_db_erase). This collection is + * called a "changeset" and implemented in changeset.h/.cc. As soon as the + * operation is finished, the changeset is flushed: if the changeset contains + * just a single page, then this operation is atomic and is NOT logged. + * Otherwise the whole changeset is appended to the journal, and afterwards + * the database file is modified. + * + * For recovery to work, each page stores the lsn of its last modification. + * + * When recovering, the Journal first extracts the newest/latest entry. + * If this entry is a changeset then the changeset is reapplied, because + * we assume that there was a crash immediately AFTER the changeset was + * written, but BEFORE the database file was modified. (The changeset is + * idempotent; if the database file was successfully modified then the + * changes are re-applied; this is not a problem.) + * + * Afterwards, hamsterdb uses the lsn's to figure out whether an update + * was already applied or not. If the journal's last entry is a changeset then + * this changeset's lsn marks the beginning of the sequence. Otherwise the lsn + * is fetched from the journal file headers. All journal entries with an lsn + * *older* than this start-lsn will be skipped, all others are re-applied. + * + * In this phase all changesets are skipped because the newest changeset was + * already applied, and we know that all older changesets + * have already been written successfully to the database file. + * + * @exception_safe: basic + * @thread_safe: no + */ + +#ifndef HAM_JOURNAL_H +#define HAM_JOURNAL_H + +#include "0root/root.h" + +#include <map> +#include <cstdio> +#include <string> + +#include "ham/hamsterdb_int.h" // for metrics + +#include "1base/dynamic_array.h" +#include "1os/file.h" +#include "1errorinducer/errorinducer.h" +#include "2page/page_collection.h" +#include "3journal/journal_entries.h" +#include "3journal/journal_state.h" +#include "3journal/journal_test.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class Page; +class Database; +class Transaction; +class LocalEnvironment; +class LocalTransaction; +class LocalTransactionManager; + +#include "1base/packstart.h" + +// +// The Journal object +// +class Journal +{ + public: + enum { + // marks the start of a new transaction + kEntryTypeTxnBegin = 1, + + // marks the end of an aborted transaction + kEntryTypeTxnAbort = 2, + + // marks the end of an committed transaction + kEntryTypeTxnCommit = 3, + + // marks an insert operation + kEntryTypeInsert = 4, + + // marks an erase operation + kEntryTypeErase = 5, + + // marks a whole changeset operation (writes modified pages) + kEntryTypeChangeset = 6 + }; + + // + // An "iterator" structure for traversing the journal files + // + struct Iterator { + Iterator() + : fdidx(0), fdstart(0), offset(0) { + } + + // selects the file descriptor [0..1] + int fdidx; + + // which file descriptor did we start with? [0..1] + int fdstart; + + // the offset in the file of the NEXT entry + uint64_t offset; + }; + + // Constructor + Journal(LocalEnvironment *env); + + // Creates a new journal + void create(); + + // Opens an existing journal + void open(); + + // Returns true if the journal is empty + bool is_empty() { + if (!m_state.files[0].is_open() && !m_state.files[1].is_open()) + return (true); + + for (int i = 0; i < 2; i++) { + uint64_t size = m_state.files[i].get_file_size(); + if (size > 0) + return (false); + } + + return (true); + } + + // Appends a journal entry for ham_txn_begin/kEntryTypeTxnBegin + void append_txn_begin(LocalTransaction *txn, const char *name, + uint64_t lsn); + + // Appends a journal entry for ham_txn_abort/kEntryTypeTxnAbort + void append_txn_abort(LocalTransaction *txn, uint64_t lsn); + + // Appends a journal entry for ham_txn_commit/kEntryTypeTxnCommit + void append_txn_commit(LocalTransaction *txn, uint64_t lsn); + + // Appends a journal entry for ham_insert/kEntryTypeInsert + void append_insert(Database *db, LocalTransaction *txn, + ham_key_t *key, ham_record_t *record, uint32_t flags, + uint64_t lsn); + + // Appends a journal entry for ham_erase/kEntryTypeErase + void append_erase(Database *db, LocalTransaction *txn, + ham_key_t *key, int duplicate_index, uint32_t flags, + uint64_t lsn); + + // Appends a journal entry for a whole changeset/kEntryTypeChangeset + void append_changeset(const Page **pages, int num_pages, uint64_t lsn); + + // Adjusts the transaction counters; called whenever |txn| is flushed. + void transaction_flushed(LocalTransaction *txn); + + // Empties the journal, removes all entries + void clear() { + for (int i = 0; i < 2; i++) + clear_file(i); + } + + // Closes the journal, frees all allocated resources + void close(bool noclear = false); + + // Performs the recovery! All committed Transactions will be re-applied, + // all others are automatically aborted + void recover(LocalTransactionManager *txn_manager); + + // Fills the metrics + void fill_metrics(ham_env_metrics_t *metrics) { + metrics->journal_bytes_flushed = m_state.count_bytes_flushed; + } + + private: + friend struct JournalFixture; + + // Returns a pointer to database. If the database was not yet opened then + // it is opened implicitly. + Database *get_db(uint16_t dbname); + + // Returns a pointer to a Transaction object. + Transaction *get_txn(LocalTransactionManager *txn_manager, uint64_t txn_id); + + // Closes all databases. + void close_all_databases(); + + // Aborts all transactions which are still active. + void abort_uncommitted_txns(LocalTransactionManager *txn_manager); + + // Helper function which adds a single page from the changeset to + // the Journal; returns the page size (or compressed size, if compression + // was enabled) + uint32_t append_changeset_page(const Page *page, uint32_t page_size); + + // Recovers (re-applies) the physical changelog; returns the lsn of the + // Changelog + uint64_t recover_changeset(); + + // Scans a file for the newest changeset. Returns the lsn of this + // changeset, and the position (offset) in the file + uint64_t scan_for_newest_changeset(File *file, uint64_t *position); + + // Recovers the logical journal + void recover_journal(Context *context, + LocalTransactionManager *txn_manager, uint64_t start_lsn); + + // Switches the log file if necessary; returns the new log descriptor in the + // transaction + int switch_files_maybe(); + + // returns the path of the journal file + std::string get_path(int i); + + // Sequentially returns the next journal entry, starting with + // the oldest entry. + // + // |iter| must be initialized with zeroes for the first call. + // |auxbuffer| returns the auxiliary data of the entry and is either + // a structure of type PJournalEntryInsert or PJournalEntryErase. + // + // Returns an empty entry (lsn is zero) after the last element. + void get_entry(Iterator *iter, PJournalEntry *entry, + ByteArray *auxbuffer); + + // Appends an entry to the journal + void append_entry(int idx, + const uint8_t *ptr1 = 0, size_t ptr1_size = 0, + const uint8_t *ptr2 = 0, size_t ptr2_size = 0, + const uint8_t *ptr3 = 0, size_t ptr3_size = 0, + const uint8_t *ptr4 = 0, size_t ptr4_size = 0, + const uint8_t *ptr5 = 0, size_t ptr5_size = 0) { + if (ptr1_size) + m_state.buffer[idx].append(ptr1, ptr1_size); + if (ptr2_size) + m_state.buffer[idx].append(ptr2, ptr2_size); + if (ptr3_size) + m_state.buffer[idx].append(ptr3, ptr3_size); + if (ptr4_size) + m_state.buffer[idx].append(ptr4, ptr4_size); + if (ptr5_size) + m_state.buffer[idx].append(ptr5, ptr5_size); + } + + // flush buffer if size limit is exceeded + void maybe_flush_buffer(int idx) { + if (m_state.buffer[idx].get_size() >= JournalState::kBufferLimit) + flush_buffer(idx); + } + + // Flushes a buffer to disk + void flush_buffer(int idx, bool fsync = false) { + if (m_state.buffer[idx].get_size() > 0) { + // error inducer? then write only a part of the buffer and return + if (ErrorInducer::is_active() + && ErrorInducer::get_instance()->induce(ErrorInducer::kChangesetFlush)) { + m_state.files[idx].write(m_state.buffer[idx].get_ptr(), + m_state.buffer[idx].get_size() - 5); + throw Exception(HAM_INTERNAL_ERROR); + } + + m_state.files[idx].write(m_state.buffer[idx].get_ptr(), + m_state.buffer[idx].get_size()); + m_state.count_bytes_flushed += m_state.buffer[idx].get_size(); + + m_state.buffer[idx].clear(); + if (fsync) + m_state.files[idx].flush(); + } + } + + // Clears a single file + void clear_file(int idx); + + // Returns the test object + JournalTest test(); + + private: + // The mutable state + JournalState m_state; +}; + +#include "1base/packstop.h" + +} // namespace hamsterdb + +#endif /* HAM_JOURNAL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_entries.h b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_entries.h new file mode 100644 index 0000000000..b32f53693b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_entries.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * journal entries for insert, erase, begin, commit, abort... + * + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_JOURNAL_ENTRIES_H +#define HAM_JOURNAL_ENTRIES_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +#include "1base/packstart.h" + +/* + * A journal entry for all txn related operations (begin, commit, abort) + * + * This structure can be followed by one of the structures below + * (PJournalEntryInsert or PJournalEntryERASE); the field |followup_size| + * is the structure size of this follow-up structure. + */ +HAM_PACK_0 struct HAM_PACK_1 PJournalEntry { + // Constructor - sets all fields to 0 + PJournalEntry() + : lsn(0), followup_size(0), txn_id(0), type(0), + dbname(0), _reserved(0) { + } + + // the lsn of this entry + uint64_t lsn; + + // the size of the follow-up entry in bytes (may be padded) + uint64_t followup_size; + + // the transaction id + uint64_t txn_id; + + // the type of this entry + uint32_t type; + + // the name of the database which is modified by this entry + uint16_t dbname; + + // a reserved value - reqd for padding + uint16_t _reserved; +} HAM_PACK_2; + +#include "1base/packstop.h" + + +#include "1base/packstart.h" + +// +// a Journal entry for an 'insert' operation +// +HAM_PACK_0 struct HAM_PACK_1 PJournalEntryInsert { + // Constructor - sets all fields to 0 + PJournalEntryInsert() + : key_size(0), compressed_key_size(0), record_size(0), + compressed_record_size(0), record_partial_size(0), + record_partial_offset(0), insert_flags(0) { + data[0] = 0; + } + + // key size + uint16_t key_size; + + // PRO: compressed key size + uint16_t compressed_key_size; + + // record size + uint32_t record_size; + + // PRO: compressed record size + uint32_t compressed_record_size; + + // record partial size + uint32_t record_partial_size; + + // record partial offset + uint32_t record_partial_offset; + + // flags of ham_insert(), ham_cursor_insert() + uint32_t insert_flags; + + // data follows here - first |key_size| bytes for the key, then + // |record_size| bytes for the record (and maybe some padding) + // + // PRO: this data can be compressed + uint8_t data[1]; + + // Returns a pointer to the key data + uint8_t *get_key_data() { + return (&data[0]); + } + + // Returns a pointer to the record data + uint8_t *get_record_data() { + return (&data[key_size]); + } +} HAM_PACK_2; + +#include "1base/packstop.h" + + +#include "1base/packstart.h" + +// +// a Journal entry for 'erase' operations +// +HAM_PACK_0 struct HAM_PACK_1 PJournalEntryErase { + // Constructor - sets all fields to 0 + PJournalEntryErase() + : key_size(0), compressed_key_size(0), erase_flags(0), duplicate(0) { + data[0] = 0; + } + + // key size + uint16_t key_size; + + // PRO: compressed key size + uint16_t compressed_key_size; + + // flags of ham_erase(), ham_cursor_erase() + uint32_t erase_flags; + + // which duplicate to erase + int duplicate; + + // the key data + // + // PRO: this data can be compressed + uint8_t data[1]; + + // Returns a pointer to the key data + uint8_t *get_key_data() { + return (&data[0]); + } +} HAM_PACK_2; + +#include "1base/packstop.h" + + +#include "1base/packstart.h" + +// +// a Journal entry for a 'changeset' group +// +HAM_PACK_0 struct HAM_PACK_1 PJournalEntryChangeset { + // Constructor - sets all fields to 0 + PJournalEntryChangeset() + : num_pages(0) { + } + + // number of pages in this changeset + uint32_t num_pages; +} HAM_PACK_2; + +#include "1base/packstop.h" + + +#include "1base/packstart.h" + +// +// a Journal entry for a single page +// +HAM_PACK_0 struct HAM_PACK_1 PJournalEntryPageHeader { + // Constructor - sets all fields to 0 + PJournalEntryPageHeader(uint64_t _address = 0) + : address(_address), compressed_size(0) { + } + + // the page address + uint64_t address; + + // PRO: the compressed size, if compression is enabled + uint32_t compressed_size; +} HAM_PACK_2; + +#include "1base/packstop.h" + +} // namespace hamsterdb + +#endif /* HAM_JOURNAL_ENTRIES_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_state.h b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_state.h new file mode 100644 index 0000000000..817fcac1d5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_state.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The Journal's state + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_JOURNAL_STATE_H +#define HAM_JOURNAL_STATE_H + +#include "0root/root.h" + +#include <map> +#include <string> + +#include "ham/hamsterdb_int.h" // for metrics + +#include "1base/dynamic_array.h" +#include "1os/file.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Database; +class LocalEnvironment; + +struct JournalState +{ + enum { + // switch log file after |kSwitchTxnThreshold| transactions + kSwitchTxnThreshold = 32, + + // flush buffers if this limit is exceeded + kBufferLimit = 1024 * 1024 // 1 mb + }; + + JournalState(LocalEnvironment *env); + + // References the Environment this journal file is for + LocalEnvironment *env; + + // The index of the file descriptor we are currently writing to (0 or 1) + uint32_t current_fd; + + // The two file descriptors + File files[2]; + + // Buffers for writing data to the files + ByteArray buffer[2]; + + // For counting all open transactions in the files + size_t open_txn[2]; + + // For counting all closed transactions in the files + size_t closed_txn[2]; + + // The lsn of the previous checkpoint + uint64_t last_cp_lsn; + + // When having more than these Transactions in one file, we + // swap the files + size_t threshold; + + // Set to false to disable logging; used during recovery + bool disable_logging; + + // Counting the flushed bytes (for ham_env_get_metrics) + uint64_t count_bytes_flushed; + + // Counting the bytes before compression (for ham_env_get_metrics) + uint64_t count_bytes_before_compression; + + // Counting the bytes after compression (for ham_env_get_metrics) + uint64_t count_bytes_after_compression; + + // A map of all opened Databases + typedef std::map<uint16_t, Database *> DatabaseMap; + DatabaseMap database_map; +}; + +} // namespace hamsterdb + +#endif /* HAM_JOURNAL_STATE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_test.h b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_test.h new file mode 100644 index 0000000000..464d8fa43c --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3journal/journal_test.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Test gateway for the Journal + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_JOURNAL_TEST_H +#define HAM_JOURNAL_TEST_H + +#include "0root/root.h" + +#include "ham/hamsterdb_int.h" // for metrics + +#include "3journal/journal_state.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class JournalTest +{ + public: + JournalTest(JournalState *state) + : m_state(state) { + } + + // Returns the state + JournalState *state() { return (m_state); } + + private: + // The journal's state + JournalState *m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_JOURNAL_TEST_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.cc b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.cc new file mode 100644 index 0000000000..bec3cc32e0 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.cc @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "1base/pickle.h" +#include "2page/page.h" +#include "2device/device.h" +#include "2queue/queue.h" +#include "3page_manager/page_manager.h" +#include "3page_manager/page_manager_worker.h" +#include "3page_manager/page_manager_test.h" +#include "3btree/btree_index.h" +#include "3btree/btree_node_proxy.h" +#include "4context/context.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +enum { + kPurgeAtLeast = 20 +}; + +PageManagerState::PageManagerState(LocalEnvironment *env) + : config(env->config()), header(env->header()), + device(env->device()), lsn_manager(env->lsn_manager()), + cache(env->config()), needs_flush(false), purge_cache_pending(false), + state_page(0), last_blob_page(0), last_blob_page_id(0), + page_count_fetched(0), page_count_index(0), page_count_blob(0), + page_count_page_manager(0), cache_hits(0), cache_misses(0), + freelist_hits(0), freelist_misses(0) +{ +} + +PageManager::PageManager(LocalEnvironment *env) + : m_state(env) +{ + /* start the worker thread */ + m_worker.reset(new PageManagerWorker(&m_state.cache)); +} + +void +PageManager::initialize(uint64_t pageid) +{ + Context context(0, 0, 0); + + m_state.free_pages.clear(); + if (m_state.state_page) + delete m_state.state_page; + m_state.state_page = new Page(m_state.device); + m_state.state_page->fetch(pageid); + + Page *page = m_state.state_page; + uint32_t page_size = m_state.config.page_size_bytes; + + // the first page stores the page ID of the last blob + m_state.last_blob_page_id = *(uint64_t *)page->get_payload(); + + while (1) { + ham_assert(page->get_type() == Page::kTypePageManager); + uint8_t *p = page->get_payload(); + // skip m_state.last_blob_page_id? + if (page == m_state.state_page) + p += sizeof(uint64_t); + + // get the overflow address + uint64_t overflow = *(uint64_t *)p; + p += 8; + + // get the number of stored elements + uint32_t counter = *(uint32_t *)p; + p += 4; + + // now read all pages + for (uint32_t i = 0; i < counter; i++) { + // 4 bits page_counter, 4 bits for number of following bytes + int page_counter = (*p & 0xf0) >> 4; + int num_bytes = *p & 0x0f; + ham_assert(page_counter > 0); + ham_assert(num_bytes <= 8); + p += 1; + + uint64_t id = Pickle::decode_u64(num_bytes, p); + p += num_bytes; + + m_state.free_pages[id * page_size] = page_counter; + } + + // load the overflow page + if (overflow) + page = fetch(&context, overflow, 0); + else + break; + } +} + +Page * +PageManager::fetch(Context *context, uint64_t address, uint32_t flags) +{ + /* fetch the page from the cache */ + Page *page; + + if (address == 0) + page = m_state.header->get_header_page(); + else + page = m_state.cache.get(address); + + if (page) { + if (flags & PageManager::kNoHeader) + page->set_without_header(true); + return (safely_lock_page(context, page, true)); + } + + if ((flags & PageManager::kOnlyFromCache) + || m_state.config.flags & HAM_IN_MEMORY) + return (0); + + page = new Page(m_state.device, context->db); + try { + page->fetch(address); + } + catch (Exception &ex) { + delete page; + throw ex; + } + + ham_assert(page->get_data()); + + /* store the page in the list */ + m_state.cache.put(page); + + /* write to disk (if necessary) */ + if (!(flags & PageManager::kDisableStoreState) + && !(flags & PageManager::kReadOnly)) + maybe_store_state(context, false); + + if (flags & PageManager::kNoHeader) + page->set_without_header(true); + + m_state.page_count_fetched++; + return (safely_lock_page(context, page, false)); +} + +Page * +PageManager::alloc(Context *context, uint32_t page_type, uint32_t flags) +{ + uint64_t address = 0; + Page *page = 0; + uint32_t page_size = m_state.config.page_size_bytes; + bool allocated = false; + + /* first check the internal list for a free page */ + if ((flags & PageManager::kIgnoreFreelist) == 0 + && !m_state.free_pages.empty()) { + PageManagerState::FreeMap::iterator it = m_state.free_pages.begin(); + + address = it->first; + ham_assert(address % page_size == 0); + /* remove the page from the freelist */ + m_state.free_pages.erase(it); + m_state.needs_flush = true; + + m_state.freelist_hits++; + + /* try to fetch the page from the cache */ + page = m_state.cache.get(address); + if (page) + goto done; + /* allocate a new page structure and read the page from disk */ + page = new Page(m_state.device, context->db); + page->fetch(address); + goto done; + } + + m_state.freelist_misses++; + + try { + if (!page) { + allocated = true; + page = new Page(m_state.device, context->db); + } + + page->alloc(page_type); + } + catch (Exception &ex) { + if (allocated) + delete page; + throw ex; + } + +done: + /* clear the page with zeroes? */ + if (flags & PageManager::kClearWithZero) + memset(page->get_data(), 0, page_size); + + /* initialize the page; also set the 'dirty' flag to force logging */ + page->set_type(page_type); + page->set_dirty(true); + page->set_db(context->db); + + if (page->get_node_proxy()) { + delete page->get_node_proxy(); + page->set_node_proxy(0); + } + + /* store the page in the cache and the Changeset */ + m_state.cache.put(page); + safely_lock_page(context, page, false); + + /* write to disk (if necessary) */ + if (!(flags & PageManager::kDisableStoreState) + && !(flags & PageManager::kReadOnly)) + maybe_store_state(context, false); + + switch (page_type) { + case Page::kTypeBindex: + case Page::kTypeBroot: { + memset(page->get_payload(), 0, sizeof(PBtreeNode)); + m_state.page_count_index++; + break; + } + case Page::kTypePageManager: + m_state.page_count_page_manager++; + break; + case Page::kTypeBlob: + m_state.page_count_blob++; + break; + default: + break; + } + + return (page); +} + +Page * +PageManager::alloc_multiple_blob_pages(Context *context, size_t num_pages) +{ + // allocate only one page? then use the normal ::alloc() method + if (num_pages == 1) + return (alloc(context, Page::kTypeBlob, 0)); + + Page *page = 0; + uint32_t page_size = m_state.config.page_size_bytes; + + // Now check the freelist + if (!m_state.free_pages.empty()) { + for (PageManagerState::FreeMap::iterator it = m_state.free_pages.begin(); + it != m_state.free_pages.end(); + it++) { + if (it->second >= num_pages) { + for (size_t i = 0; i < num_pages; i++) { + if (i == 0) { + page = fetch(context, it->first, 0); + page->set_type(Page::kTypeBlob); + page->set_without_header(false); + } + else { + Page *p = fetch(context, it->first + (i * page_size), 0); + p->set_type(Page::kTypeBlob); + p->set_without_header(true); + } + } + if (it->second > num_pages) { + m_state.free_pages[it->first + num_pages * page_size] + = it->second - num_pages; + } + m_state.free_pages.erase(it); + return (page); + } + } + } + + // Freelist lookup was not successful -> allocate new pages. Only the first + // page is a regular page; all others do not have page headers. + // + // disable "store state": the PageManager otherwise could alloc overflow + // pages in the middle of our blob sequence. + uint32_t flags = PageManager::kIgnoreFreelist + | PageManager::kDisableStoreState; + for (size_t i = 0; i < num_pages; i++) { + if (page == 0) + page = alloc(context, Page::kTypeBlob, flags); + else { + Page *p = alloc(context, Page::kTypeBlob, flags); + p->set_without_header(true); + } + } + + // now store the state + maybe_store_state(context, false); + return (page); +} + +void +PageManager::fill_metrics(ham_env_metrics_t *metrics) const +{ + metrics->page_count_fetched = m_state.page_count_fetched; + metrics->page_count_flushed = Page::ms_page_count_flushed; + metrics->page_count_type_index = m_state.page_count_index; + metrics->page_count_type_blob = m_state.page_count_blob; + metrics->page_count_type_page_manager = m_state.page_count_page_manager; + metrics->freelist_hits = m_state.freelist_hits; + metrics->freelist_misses = m_state.freelist_misses; + m_state.cache.fill_metrics(metrics); +} + +struct FlushAllPagesPurger +{ + FlushAllPagesPurger(bool delete_pages) + : delete_pages(delete_pages) { + } + + bool operator()(Page *page) { + ScopedSpinlock lock(page->mutex()); + page->flush(); + return (delete_pages); + } + + bool delete_pages; +}; + +void +PageManager::flush(bool delete_pages) +{ + FlushAllPagesPurger purger(delete_pages); + m_state.cache.purge_if(purger); + + if (m_state.state_page) { + ScopedSpinlock lock(m_state.state_page->mutex()); + m_state.state_page->flush(); + } +} + +// Returns true if the page can be purged: page must use allocated +// memory instead of an mmapped pointer; page must not be in use (= in +// a changeset) and not have cursors attached +struct PurgeProcessor +{ + PurgeProcessor(Page *last_blob_page, FlushPageMessage *message) + : last_blob_page(last_blob_page), message(message) { + } + + bool operator()(Page *page) { + // the lock in here will be unlocked by the worker thread + if (page == last_blob_page || !page->mutex().try_lock()) + return (false); + message->list.push_back(page); + return (true); + } + + Page *last_blob_page; + FlushPageMessage *message; +}; + +void +PageManager::purge_cache(Context *context) +{ + // do NOT purge the cache iff + // 1. this is an in-memory Environment + // 2. there's still a "purge cache" operation pending + // 3. the cache is not full + if (m_state.config.flags & HAM_IN_MEMORY + || m_state.purge_cache_pending + || !m_state.cache.is_cache_full()) + return; + + // Purge as many pages as possible to get memory usage down to the + // cache's limit. + FlushPageMessage *message = new FlushPageMessage(); + PurgeProcessor processor(m_state.last_blob_page, message); + m_state.cache.purge(processor, m_state.last_blob_page); + + if (message->list.size()) + m_worker->add_to_queue(message); + else + delete message; +} + +void +PageManager::reclaim_space(Context *context) +{ + if (m_state.last_blob_page) { + m_state.last_blob_page_id = m_state.last_blob_page->get_address(); + m_state.last_blob_page = 0; + } + ham_assert(!(m_state.config.flags & HAM_DISABLE_RECLAIM_INTERNAL)); + + bool do_truncate = false; + size_t file_size = m_state.device->file_size(); + uint32_t page_size = m_state.config.page_size_bytes; + + while (m_state.free_pages.size() > 1) { + PageManagerState::FreeMap::iterator fit = + m_state.free_pages.find(file_size - page_size); + if (fit != m_state.free_pages.end()) { + Page *page = m_state.cache.get(fit->first); + if (page) { + m_state.cache.del(page); + delete page; + } + file_size -= page_size; + do_truncate = true; + m_state.free_pages.erase(fit); + continue; + } + break; + } + + if (do_truncate) { + m_state.needs_flush = true; + maybe_store_state(context, true); + m_state.device->truncate(file_size); + } +} + +struct DbClosePurger +{ + DbClosePurger(LocalDatabase *db) + : m_db(db) { + } + + bool operator()(Page *page) { + if (page->get_db() == m_db && page->get_address() != 0) { + ScopedSpinlock lock(page->mutex()); + ham_assert(page->cursor_list() == 0); + page->flush(); + return (true); + } + return (false); + } + + LocalDatabase *m_db; +}; + +void +PageManager::close_database(Context *context, LocalDatabase *db) +{ + if (m_state.last_blob_page) { + m_state.last_blob_page_id = m_state.last_blob_page->get_address(); + m_state.last_blob_page = 0; + } + + context->changeset.clear(); + + DbClosePurger purger(db); + m_state.cache.purge_if(purger); +} + +void +PageManager::del(Context *context, Page *page, size_t page_count) +{ + ham_assert(page_count > 0); + + if (m_state.config.flags & HAM_IN_MEMORY) + return; + + // remove all pages from the changeset, otherwise they won't be unlocked + context->changeset.del(page); + if (page_count > 1) { + uint32_t page_size = m_state.config.page_size_bytes; + for (size_t i = 1; i < page_count; i++) { + Page *p = m_state.cache.get(page->get_address() + i * page_size); + if (p && context->changeset.has(p)) + context->changeset.del(p); + } + } + + m_state.needs_flush = true; + m_state.free_pages[page->get_address()] = page_count; + ham_assert(page->get_address() % m_state.config.page_size_bytes == 0); + + if (page->get_node_proxy()) { + delete page->get_node_proxy(); + page->set_node_proxy(0); + } + + // do not call maybe_store_state() - this change in the m_state is not + // relevant for logging. +} + +void +PageManager::reset(Context *context) +{ + close(context); + + /* start the worker thread */ + m_worker.reset(new PageManagerWorker(&m_state.cache)); +} + +void +PageManager::close(Context *context) +{ + /* wait for the worker thread to stop */ + if (m_worker.get()) + m_worker->stop_and_join(); + + // store the state of the PageManager + if ((m_state.config.flags & HAM_IN_MEMORY) == 0 + && (m_state.config.flags & HAM_READ_ONLY) == 0) { + maybe_store_state(context, true); + } + + // reclaim unused disk space + // if logging is enabled: also flush the changeset to write back the + // modified freelist pages + bool try_reclaim = m_state.config.flags & HAM_DISABLE_RECLAIM_INTERNAL + ? false + : true; + +#ifdef WIN32 + // Win32: it's not possible to truncate the file while there's an active + // mapping, therefore only reclaim if memory mapped I/O is disabled + if (!(m_state.config.flags & HAM_DISABLE_MMAP)) + try_reclaim = false; +#endif + + if (try_reclaim) { + reclaim_space(context); + } + + // clear the Changeset because flush() will delete all Page pointers + context->changeset.clear(); + + // flush all dirty pages to disk, then delete them + flush(true); + + delete m_state.state_page; + m_state.state_page = 0; + m_state.last_blob_page = 0; +} + +Page * +PageManager::get_last_blob_page(Context *context) +{ + if (m_state.last_blob_page) + return (safely_lock_page(context, m_state.last_blob_page, true)); + if (m_state.last_blob_page_id) + return (fetch(context, m_state.last_blob_page_id, 0)); + return (0); +} + +void +PageManager::set_last_blob_page(Page *page) +{ + m_state.last_blob_page_id = 0; + m_state.last_blob_page = page; +} + +uint64_t +PageManager::store_state(Context *context) +{ + // no modifications? then simply return the old blobid + if (!m_state.needs_flush) + return (m_state.state_page ? m_state.state_page->get_address() : 0); + + m_state.needs_flush = false; + + // no freelist pages, no freelist state? then don't store anything + if (!m_state.state_page && m_state.free_pages.empty()) + return (0); + + // otherwise allocate a new page, if required + if (!m_state.state_page) { + m_state.state_page = new Page(m_state.device); + m_state.state_page->alloc(Page::kTypePageManager, + Page::kInitializeWithZeroes); + } + + // don't bother locking the state page + context->changeset.put(m_state.state_page); + + uint32_t page_size = m_state.config.page_size_bytes; + + // make sure that the page is logged + Page *page = m_state.state_page; + page->set_dirty(true); + + uint8_t *p = page->get_payload(); + + // store page-ID of the last allocated blob + *(uint64_t *)p = m_state.last_blob_page_id; + p += sizeof(uint64_t); + + // reset the overflow pointer and the counter + // TODO here we lose a whole chain of overflow pointers if there was such + // a chain. We only save the first. That's not critical but also not nice. + uint64_t next_pageid = *(uint64_t *)p; + if (next_pageid) { + m_state.free_pages[next_pageid] = 1; + ham_assert(next_pageid % page_size == 0); + } + + // No freelist entries? then we're done. Make sure that there's no + // overflow pointer or other garbage in the page! + if (m_state.free_pages.empty()) { + *(uint64_t *)p = 0; + p += sizeof(uint64_t); + *(uint32_t *)p = 0; + return (m_state.state_page->get_address()); + } + + PageManagerState::FreeMap::const_iterator it = m_state.free_pages.begin(); + while (it != m_state.free_pages.end()) { + // this is where we will store the data + p = page->get_payload(); + // skip m_state.last_blob_page_id? + if (page == m_state.state_page) + p += sizeof(uint64_t); + p += 8; // leave room for the pointer to the next page + p += 4; // leave room for the counter + + uint32_t counter = 0; + + while (it != m_state.free_pages.end()) { + // 9 bytes is the maximum amount of storage that we will need for a + // new entry; if it does not fit then break + if ((p + 9) - page->get_payload() + >= (ptrdiff_t)(m_state.config.page_size_bytes + - Page::kSizeofPersistentHeader)) + break; + + // ... and check if the next entry (and the following) are directly + // next to the current page + uint32_t page_counter = 1; + uint64_t base = it->first; + ham_assert(base % page_size == 0); + uint64_t current = it->first; + + // move to the next entry + it++; + + for (; it != m_state.free_pages.end() && page_counter < 16 - 1; it++) { + if (it->first != current + page_size) + break; + current += page_size; + page_counter++; + } + + // now |base| is the start of a sequence of free pages, and the + // sequence has |page_counter| pages + // + // This is encoded as + // - 1 byte header + // - 4 bits for |page_counter| + // - 4 bits for the number of bytes following ("n") + // - n byte page-id (div page_size) + ham_assert(page_counter < 16); + int num_bytes = Pickle::encode_u64(p + 1, base / page_size); + *p = (page_counter << 4) | num_bytes; + p += 1 + num_bytes; + + counter++; + } + + p = page->get_payload(); + if (page == m_state.state_page) // skip m_state.last_blob_page_id? + p += sizeof(uint64_t); + uint64_t next_pageid = *(uint64_t *)p; + *(uint64_t *)p = 0; + p += 8; // overflow page + + // now store the counter + *(uint32_t *)p = counter; + + // are we done? if not then continue with the next page + if (it != m_state.free_pages.end()) { + // allocate (or fetch) an overflow page + if (!next_pageid) { + Page *new_page = alloc(context, Page::kTypePageManager, + PageManager::kIgnoreFreelist); + // patch the overflow pointer in the old (current) page + p = page->get_payload(); + if (page == m_state.state_page) // skip m_state.last_blob_page_id? + p += sizeof(uint64_t); + *(uint64_t *)p = new_page->get_address(); + + // reset the overflow pointer in the new page + page = new_page; + p = page->get_payload(); + *(uint64_t *)p = 0; + } + else + page = fetch(context, next_pageid, 0); + + // make sure that the page is logged + page->set_dirty(true); + } + } + + return (m_state.state_page->get_address()); +} + +void +PageManager::maybe_store_state(Context *context, bool force) +{ + if (force || (m_state.config.flags & HAM_ENABLE_RECOVERY)) { + uint64_t new_blobid = store_state(context); + if (new_blobid != m_state.header->get_page_manager_blobid()) { + m_state.header->set_page_manager_blobid(new_blobid); + // don't bother to lock the header page + m_state.header->get_header_page()->set_dirty(true); + context->changeset.put(m_state.header->get_header_page()); + } + } +} + +Page * +PageManager::safely_lock_page(Context *context, Page *page, + bool allow_recursive_lock) +{ + context->changeset.put(page); + + ham_assert(page->mutex().try_lock() == false); + + // fetch contents again? + if (!page->get_data()) { + page->fetch(page->get_address()); + } + + return (page); +} + +PageManagerTest +PageManager::test() +{ + return (PageManagerTest(this)); +} + +PageManagerTest::PageManagerTest(PageManager *page_manager) + : m_sut(page_manager) +{ +} + +uint64_t +PageManagerTest::store_state() +{ + Context context(0, 0, 0); + return (m_sut->store_state(&context)); +} + +void +PageManagerTest::remove_page(Page *page) +{ + m_sut->m_state.cache.del(page); +} + +bool +PageManagerTest::is_page_free(uint64_t pageid) +{ + return (m_sut->m_state.free_pages.find(pageid) + != m_sut->m_state.free_pages.end()); +} + +Page * +PageManagerTest::fetch_page(uint64_t id) +{ + return (m_sut->m_state.cache.get(id)); +} + +void +PageManagerTest::store_page(Page *page) +{ + m_sut->m_state.cache.put(page); +} + +bool +PageManagerTest::is_cache_full() +{ + return (m_sut->m_state.cache.is_cache_full()); +} + +PageManagerState * +PageManagerTest::state() +{ + return (&m_sut->m_state); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.h b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.h new file mode 100644 index 0000000000..a6593e39ae --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The PageManager allocates, fetches and frees pages. It manages the + * list of all pages (free and not free), and maps their virtual ID to + * their physical address in the file. + * + * @exception_safe: basic + * @thread_safe: no + */ + +#ifndef HAM_PAGE_MANAGER_H +#define HAM_PAGE_MANAGER_H + +#include "0root/root.h" + +#include <map> + +// Always verify that a file of level N does not include headers > N! +#include "1base/scoped_ptr.h" +#include "3page_manager/page_manager_state.h" +#include "3page_manager/page_manager_test.h" +#include "3page_manager/page_manager_worker.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class LocalDatabase; +class LocalEnvironment; + +class PageManager +{ + public: + enum { + // flag for alloc(): Clear the full page with zeroes + kClearWithZero = 1, + + // flag for alloc(): Ignores the freelist + kIgnoreFreelist = 2, + + // flag for alloc(): Do not persist the PageManager state to disk + kDisableStoreState = 4, + + // Flag for fetch(): only fetches from cache, not from disk + kOnlyFromCache = 1, + + // Flag for fetch(): does not add page to the Changeset + kReadOnly = 2, + + // Flag for fetch(): page is part of a multi-page blob, has no header + kNoHeader = 4 + }; + + // Constructor + PageManager(LocalEnvironment *env); + + // Loads the state from a blob + void initialize(uint64_t blobid); + + // Fills in the current metrics for the PageManager, the Cache and the + // Freelist + void fill_metrics(ham_env_metrics_t *metrics) const; + + // Fetches a page from disk. |flags| are bitwise OR'd: kOnlyFromCache, + // kReadOnly, kNoHeader... + // The page is locked and stored in |context->changeset|. + Page *fetch(Context *context, uint64_t address, uint32_t flags = 0); + + // Allocates a new page. |page_type| is one of Page::kType* in page.h. + // |flags| are either 0 or kClearWithZero + // The page is locked and stored in |context->changeset|. + Page *alloc(Context *context, uint32_t page_type, uint32_t flags = 0); + + // Allocates multiple adjacent pages. + // Used by the BlobManager to store blobs that span multiple pages + // Returns the first page in the list of pages + // The pages are locked and stored in |context->changeset|. + Page *alloc_multiple_blob_pages(Context *context, size_t num_pages); + + // Flushes all pages to disk and deletes them if |delete_pages| is true + void flush(bool delete_pages); + + // Asks the worker thread to purge the cache if the cache limits are + // exceeded + void purge_cache(Context *context); + + // Reclaim file space; truncates unused file space at the end of the file. + void reclaim_space(Context *context); + + // Flushes and closes all pages of a database + void close_database(Context *context, LocalDatabase *db); + + // Schedules one (or many sequential) pages for deletion and adds them + // to the Freelist. Will not do anything if the Environment is in-memory. + void del(Context *context, Page *page, size_t page_count = 1); + + // Resets the PageManager; calls clear(), then starts a new worker thread + void reset(Context *context); + + // Closes the PageManager; flushes all dirty pages + void close(Context *context); + + // Returns the Page pointer where we can add more blobs + Page *get_last_blob_page(Context *context); + + // Sets the Page pointer where we can add more blobs + void set_last_blob_page(Page *page); + + // Returns additional testing interfaces + PageManagerTest test(); + + private: + friend struct Purger; + friend class PageManagerTest; + friend class PageManagerWorker; + + // Persists the PageManager's state in the file + uint64_t store_state(Context *context); + + // Calls store_state() whenever it makes sense + void maybe_store_state(Context *context, bool force); + + // Locks a page, fetches contents from disk if they were flushed in + // the meantime + Page *safely_lock_page(Context *context, Page *page, + bool allow_recursive_lock); + + // The worker thread which flushes dirty pages + ScopedPtr<PageManagerWorker> m_worker; + + // The state + PageManagerState m_state; +}; + +} // namespace hamsterdb + +#endif /* HAM_PAGE_MANAGER_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_state.h b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_state.h new file mode 100644 index 0000000000..dc02b02b79 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_state.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The PageManager allocates, fetches and frees pages. It manages the + * list of all pages (free and not free), and maps their virtual ID to + * their physical address in the file. + * + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_PAGE_MANAGER_STATE_H +#define HAM_PAGE_MANAGER_STATE_H + +#include "0root/root.h" + +#include <map> +#include <boost/atomic.hpp> + +// Always verify that a file of level N does not include headers > N! +#include "2config/env_config.h" +#include "3cache/cache.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Device; +class EnvironmentHeader; +class LocalDatabase; +class LocalEnvironment; +class LsnManager; + +/* + * The internal state of the PageManager + */ +struct PageManagerState +{ + // The freelist maps page-id to number of free pages (usually 1) + typedef std::map<uint64_t, size_t> FreeMap; + + PageManagerState(LocalEnvironment *env); + + // Copy of the Environment's configuration + const EnvironmentConfiguration config; + + // The Environment's header + EnvironmentHeader *header; + + // The Device + Device *device; + + // The lsn manager + LsnManager *lsn_manager; + + // The cache + Cache cache; + + // The map with free pages + FreeMap free_pages; + + // Whether |m_free_pages| must be flushed or not + bool needs_flush; + + // Whether a "purge cache" operation is pending + boost::atomic<bool> purge_cache_pending; + + // Page with the persisted state data. If multiple pages are allocated + // then these pages form a linked list, with |m_state_page| being the head + Page *state_page; + + // Cached page where to add more blobs + Page *last_blob_page; + + // Page where to add more blobs - if |m_last_blob_page| was flushed + uint64_t last_blob_page_id; + + // tracks number of fetched pages + uint64_t page_count_fetched; + + // tracks number of index pages + uint64_t page_count_index; + + // tracks number of blob pages + uint64_t page_count_blob; + + // tracks number of page manager pages + uint64_t page_count_page_manager; + + // tracks number of cache hits + uint64_t cache_hits; + + // tracks number of cache misses + uint64_t cache_misses; + + // number of successful freelist hits + uint64_t freelist_hits; + + // number of freelist misses + uint64_t freelist_misses; +}; + +} // namespace hamsterdb + +#endif /* HAM_PAGE_MANAGER_STATE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_test.h b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_test.h new file mode 100644 index 0000000000..741cbc8390 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_test.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A test gateway for the PageManager + * + * @exception_safe: no + * @thread_safe: no + */ + +#ifndef HAM_PAGE_MANAGER_TEST_H +#define HAM_PAGE_MANAGER_TEST_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3page_manager/page_manager_state.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Page; +class PageManager; + +class PageManagerTest +{ + public: + // Constructor + PageManagerTest(PageManager *page_manager); + + // Stores the local PageManager state to disk; returns the blob id + uint64_t store_state(); + + // Removes a page from the list; only for testing. + void remove_page(Page *page); + + // Returns true if a page is free. Ignores multi-pages; only for + // testing and integrity checks + bool is_page_free(uint64_t pageid); + + // Fetches a page from the cache + Page *fetch_page(uint64_t id); + + // Stores a page in the cache + void store_page(Page *page); + + // Returns true if the cache is full + bool is_cache_full(); + + // Returns the state + PageManagerState *state(); + + private: + // Reference of the PageManager instance + PageManager *m_sut; +}; + +} // namespace hamsterdb + +#endif /* HAM_PAGE_MANAGER_TEST_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_worker.h b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_worker.h new file mode 100644 index 0000000000..2a66189765 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/3page_manager/page_manager_worker.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The worker thread for the PageManager + */ + +#ifndef HAM_PAGE_MANAGER_WORKER_H +#define HAM_PAGE_MANAGER_WORKER_H + +#include "0root/root.h" + +#include <vector> +#include <boost/thread.hpp> +#include <boost/atomic.hpp> + +// Always verify that a file of level N does not include headers > N! +#include "2device/device.h" +#include "2queue/queue.h" +#include "2worker/worker.h" +#include "3cache/cache.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct FlushPageMessage : public MessageBase +{ + // The available message types + enum { + kFlushPage = 1, + }; + + FlushPageMessage() + : MessageBase(kFlushPage, 0) { + } + + std::vector<Page *> list; +}; + + +class PageManagerWorker : public Worker +{ + public: + PageManagerWorker(Cache *cache) + : Worker(), m_cache(cache) { + } + + private: + virtual void handle_message(MessageBase *message) { + switch (message->type) { + case FlushPageMessage::kFlushPage: { + FlushPageMessage *fpm = (FlushPageMessage *)message; + for (std::vector<Page *>::iterator it = fpm->list.begin(); + it != fpm->list.end(); + ++it) { + Page *page = *it; + ham_assert(page != 0); + ham_assert(page->mutex().try_lock() == false); + try { + page->flush(); + } + catch (Exception &) { + page->mutex().unlock(); + throw; + } + page->mutex().unlock(); + } + break; + } + default: + ham_assert(!"shouldn't be here"); + } + } + + // The PageManager's cache + Cache *m_cache; +}; + +} // namespace hamsterdb + +#endif // HAM_PAGE_MANAGER_WORKER_H diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4context/context.h b/plugins/Dbx_kv/src/hamsterdb/src/4context/context.h new file mode 100644 index 0000000000..7a88aa211e --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4context/context.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_CONTEXT_H +#define HAM_CONTEXT_H + +#include "0root/root.h" + +#include "3changeset/changeset.h" + +namespace hamsterdb { + +class Cursor; +class LocalDatabase; +class LocalEnvironment; +class LocalTransaction; + +struct Context +{ + Context(LocalEnvironment *env, LocalTransaction *txn = 0, + LocalDatabase *db = 0) + : env(env), txn(txn), db(db), changeset(env) { + } + + ~Context() { + changeset.clear(); + } + + LocalEnvironment *env; + LocalTransaction *txn; + LocalDatabase *db; + + // Each operation has its own changeset which stores all locked pages + Changeset changeset; +}; + +} // namespace hamsterdb + +#endif /* HAM_CONTEXT_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.cc b/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.cc new file mode 100644 index 0000000000..57cc80a6f6 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.cc @@ -0,0 +1,1119 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "3btree/btree_cursor.h" +#include "3btree/btree_index.h" +#include "3btree/btree_node_proxy.h" +#include "4cursor/cursor.h" +#include "4env/env_local.h" +#include "4txn/txn_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +Cursor::Cursor(LocalDatabase *db, Transaction *txn, uint32_t flags) + : m_db(db), m_txn(txn), m_txn_cursor(this), m_btree_cursor(this), + m_remote_handle(0), m_next(0), m_previous(0), m_dupecache_index(0), + m_lastop(0), m_last_cmp(0), m_flags(flags), m_is_first_use(true) +{ +} + +Cursor::Cursor(Cursor &other) + : m_db(other.m_db), m_txn_cursor(this), m_btree_cursor(this) +{ + m_txn = other.m_txn; + m_remote_handle = other.m_remote_handle; + m_next = other.m_next; + m_previous = other.m_previous; + m_dupecache_index = other.m_dupecache_index; + m_lastop = other.m_lastop; + m_last_cmp = other.m_last_cmp; + m_flags = other.m_flags; + m_is_first_use = other.m_is_first_use; + + m_btree_cursor.clone(&other.m_btree_cursor); + m_txn_cursor.clone(&other.m_txn_cursor); + + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) + other.m_dupecache.clone(&m_dupecache); +} + +void +Cursor::append_btree_duplicates(Context *context, BtreeCursor *btc, + DupeCache *dc) +{ + uint32_t count = btc->get_record_count(context, 0); + for (uint32_t i = 0; i < count; i++) + dc->append(DupeCacheLine(true, i)); +} + +void +Cursor::update_dupecache(Context *context, uint32_t what) +{ + if (!(m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS)) + return; + + /* if the cache already exists: no need to continue, it should be + * up to date */ + if (m_dupecache.get_count() != 0) + return; + + if ((what & kBtree) && (what & kTxn)) { + if (is_nil(kBtree) && !is_nil(kTxn)) { + bool equal_keys; + sync(context, 0, &equal_keys); + if (!equal_keys) + set_to_nil(kBtree); + } + } + + /* first collect all duplicates from the btree. They're already sorted, + * therefore we can just append them to our duplicate-cache. */ + if ((what & kBtree) && !is_nil(kBtree)) + append_btree_duplicates(context, &m_btree_cursor, &m_dupecache); + + /* read duplicates from the txn-cursor? */ + if ((what & kTxn) && !is_nil(kTxn)) { + TransactionOperation *op = m_txn_cursor.get_coupled_op(); + TransactionNode *node = op->get_node(); + + if (!node) + return; + + /* now start integrating the items from the transactions */ + op = node->get_oldest_op(); + while (op) { + Transaction *optxn = op->get_txn(); + /* collect all ops that are valid (even those that are + * from conflicting transactions) */ + if (!optxn->is_aborted()) { + /* a normal (overwriting) insert will overwrite ALL dupes, + * but an overwrite of a duplicate will only overwrite + * an entry in the dupecache */ + if (op->get_flags() & TransactionOperation::kInsert) { + /* all existing dupes are overwritten */ + m_dupecache.clear(); + m_dupecache.append(DupeCacheLine(false, op)); + } + else if (op->get_flags() & TransactionOperation::kInsertOverwrite) { + uint32_t ref = op->get_referenced_dupe(); + if (ref) { + ham_assert(ref <= m_dupecache.get_count()); + DupeCacheLine *e = m_dupecache.get_first_element(); + (&e[ref - 1])->set_txn_op(op); + } + else { + /* all existing dupes are overwritten */ + m_dupecache.clear(); + m_dupecache.append(DupeCacheLine(false, op)); + } + } + /* insert a duplicate key */ + else if (op->get_flags() & TransactionOperation::kInsertDuplicate) { + uint32_t of = op->get_orig_flags(); + uint32_t ref = op->get_referenced_dupe() - 1; + DupeCacheLine dcl(false, op); + if (of & HAM_DUPLICATE_INSERT_FIRST) + m_dupecache.insert(0, dcl); + else if (of & HAM_DUPLICATE_INSERT_BEFORE) { + m_dupecache.insert(ref, dcl); + } + else if (of & HAM_DUPLICATE_INSERT_AFTER) { + if (ref + 1 >= m_dupecache.get_count()) + m_dupecache.append(dcl); + else + m_dupecache.insert(ref + 1, dcl); + } + else /* default is HAM_DUPLICATE_INSERT_LAST */ + m_dupecache.append(dcl); + } + /* a normal erase will erase ALL duplicate keys */ + else if (op->get_flags() & TransactionOperation::kErase) { + uint32_t ref = op->get_referenced_dupe(); + if (ref) { + ham_assert(ref <= m_dupecache.get_count()); + m_dupecache.erase(ref - 1); + } + else { + /* all existing dupes are erased */ + m_dupecache.clear(); + } + } + else { + /* everything else is a bug! */ + ham_assert(op->get_flags() == TransactionOperation::kNop); + } + } + + /* continue with the previous/older operation */ + op = op->get_next_in_node(); + } + } +} + +void +Cursor::couple_to_dupe(uint32_t dupe_id) +{ + DupeCacheLine *e = 0; + + ham_assert(m_dupecache.get_count() >= dupe_id); + ham_assert(dupe_id >= 1); + + /* dupe-id is a 1-based index! */ + e = m_dupecache.get_element(dupe_id - 1); + if (e->use_btree()) { + couple_to_btree(); + m_btree_cursor.set_duplicate_index((uint32_t)e->get_btree_dupe_idx()); + } + else { + ham_assert(e->get_txn_op() != 0); + m_txn_cursor.couple_to_op(e->get_txn_op()); + couple_to_txnop(); + } + set_dupecache_index(dupe_id); +} + +ham_status_t +Cursor::check_if_btree_key_is_erased_or_overwritten(Context *context) +{ + ham_key_t key = {0}; + TransactionOperation *op; + // TODO not threadsafe - will leak if an exception is thrown + Cursor *clone = get_db()->cursor_clone_impl(this); + + ham_status_t st = m_btree_cursor.move(context, &key, + &get_db()->key_arena(get_txn()), 0, 0, 0); + if (st) { + get_db()->cursor_close(clone); + return (st); + } + + st = clone->m_txn_cursor.find(&key, 0); + if (st) { + get_db()->cursor_close_impl(clone); + delete clone; + return (st); + } + + op = clone->m_txn_cursor.get_coupled_op(); + if (op->get_flags() & TransactionOperation::kInsertDuplicate) + st = HAM_KEY_NOT_FOUND; + get_db()->cursor_close_impl(clone); + delete clone; + return (st); +} + +void +Cursor::sync(Context *context, uint32_t flags, bool *equal_keys) +{ + if (equal_keys) + *equal_keys = false; + + if (is_nil(kBtree)) { + if (!m_txn_cursor.get_coupled_op()) + return; + ham_key_t *key = m_txn_cursor.get_coupled_op()->get_node()->get_key(); + + if (!(flags & kSyncOnlyEqualKeys)) + flags = flags | ((flags & HAM_CURSOR_NEXT) + ? HAM_FIND_GEQ_MATCH + : HAM_FIND_LEQ_MATCH); + /* the flag |kSyncDontLoadKey| does not load the key if there's an + * approx match - it only positions the cursor */ + ham_status_t st = m_btree_cursor.find(context, key, 0, 0, 0, + kSyncDontLoadKey | flags); + /* if we had a direct hit instead of an approx. match then + * set |equal_keys| to false; otherwise Cursor::move() + * will move the btree cursor again */ + if (st == 0 && equal_keys && !ham_key_get_approximate_match_type(key)) + *equal_keys = true; + } + else if (is_nil(kTxn)) { + // TODO not threadsafe - will leak if an exception is thrown + Cursor *clone = get_db()->cursor_clone_impl(this); + clone->m_btree_cursor.uncouple_from_page(context); + ham_key_t *key = clone->m_btree_cursor.get_uncoupled_key(); + if (!(flags & kSyncOnlyEqualKeys)) + flags = flags | ((flags & HAM_CURSOR_NEXT) + ? HAM_FIND_GEQ_MATCH + : HAM_FIND_LEQ_MATCH); + + ham_status_t st = m_txn_cursor.find(key, kSyncDontLoadKey | flags); + /* if we had a direct hit instead of an approx. match then + * set |equal_keys| to false; otherwise Cursor::move() + * will move the btree cursor again */ + if (st == 0 && equal_keys && !ham_key_get_approximate_match_type(key)) + *equal_keys = true; + get_db()->cursor_close_impl(clone); + delete clone; + } +} + +ham_status_t +Cursor::move_next_dupe(Context *context) +{ + if (get_dupecache_index()) { + if (get_dupecache_index() < m_dupecache.get_count()) { + set_dupecache_index(get_dupecache_index() + 1); + couple_to_dupe(get_dupecache_index()); + return (0); + } + } + return (HAM_LIMITS_REACHED); +} + +ham_status_t +Cursor::move_previous_dupe(Context *context) +{ + if (get_dupecache_index()) { + if (get_dupecache_index() > 1) { + set_dupecache_index(get_dupecache_index() - 1); + couple_to_dupe(get_dupecache_index()); + return (0); + } + } + return (HAM_LIMITS_REACHED); +} + +ham_status_t +Cursor::move_first_dupe(Context *context) +{ + if (m_dupecache.get_count()) { + set_dupecache_index(1); + couple_to_dupe(get_dupecache_index()); + return (0); + } + return (HAM_LIMITS_REACHED); +} + +ham_status_t +Cursor::move_last_dupe(Context *context) +{ + if (m_dupecache.get_count()) { + set_dupecache_index(m_dupecache.get_count()); + couple_to_dupe(get_dupecache_index()); + return (0); + } + return (HAM_LIMITS_REACHED); +} + +static bool +__txn_cursor_is_erase(TransactionCursor *txnc) +{ + TransactionOperation *op = txnc->get_coupled_op(); + return (op + ? (op->get_flags() & TransactionOperation::kErase) != 0 + : false); +} + +int +Cursor::compare(Context *context) +{ + BtreeCursor *btrc = get_btree_cursor(); + BtreeIndex *btree = get_db()->btree_index(); + + TransactionNode *node = m_txn_cursor.get_coupled_op()->get_node(); + ham_key_t *txnk = node->get_key(); + + ham_assert(!is_nil(0)); + ham_assert(!m_txn_cursor.is_nil()); + + if (btrc->get_state() == BtreeCursor::kStateCoupled) { + Page *page; + int slot; + btrc->get_coupled_key(&page, &slot, 0); + m_last_cmp = btree->get_node_from_page(page)->compare(context, txnk, slot); + + // need to fix the sort order - we compare txnk vs page[slot], but the + // caller expects m_last_cmp to be the comparison of page[slot] vs txnk + if (m_last_cmp < 0) + m_last_cmp = +1; + else if (m_last_cmp > 0) + m_last_cmp = -1; + + return (m_last_cmp); + } + else if (btrc->get_state() == BtreeCursor::kStateUncoupled) { + m_last_cmp = btree->compare_keys(btrc->get_uncoupled_key(), txnk); + return (m_last_cmp); + } + + ham_assert(!"shouldn't be here"); + return (0); +} + +ham_status_t +Cursor::move_next_key_singlestep(Context *context) +{ + ham_status_t st = 0; + BtreeCursor *btrc = get_btree_cursor(); + + /* if both cursors point to the same key: move next with both */ + if (m_last_cmp == 0) { + if (!is_nil(kBtree)) { + st = btrc->move(context, 0, 0, 0, 0, + HAM_CURSOR_NEXT | HAM_SKIP_DUPLICATES); + if (st == HAM_KEY_NOT_FOUND || st == HAM_CURSOR_IS_NIL) { + set_to_nil(kBtree); // TODO muss raus + if (m_txn_cursor.is_nil()) + return (HAM_KEY_NOT_FOUND); + else { + couple_to_txnop(); + m_last_cmp = 1; + } + } + } + if (!m_txn_cursor.is_nil()) { + st = m_txn_cursor.move(HAM_CURSOR_NEXT); + if (st == HAM_KEY_NOT_FOUND || st==HAM_CURSOR_IS_NIL) { + set_to_nil(kTxn); // TODO muss raus + if (is_nil(kBtree)) + return (HAM_KEY_NOT_FOUND); + else { + couple_to_btree(); + m_last_cmp = -1; + + ham_status_t st2 = check_if_btree_key_is_erased_or_overwritten(context); + if (st2 == HAM_TXN_CONFLICT) + st = st2; + } + } + } + } + /* if the btree-key is smaller: move it next */ + else if (m_last_cmp < 0) { + st = btrc->move(context, 0, 0, 0, 0, HAM_CURSOR_NEXT | HAM_SKIP_DUPLICATES); + if (st == HAM_KEY_NOT_FOUND) { + set_to_nil(kBtree); // TODO Das muss raus! + if (m_txn_cursor.is_nil()) + return (st); + couple_to_txnop(); + m_last_cmp = +1; + } + else { + ham_status_t st2 = check_if_btree_key_is_erased_or_overwritten(context); + if (st2 == HAM_TXN_CONFLICT) + st = st2; + } + if (m_txn_cursor.is_nil()) + m_last_cmp = -1; + } + /* if the txn-key is smaller OR if both keys are equal: move next + * with the txn-key (which is chronologically newer) */ + else { + st = m_txn_cursor.move(HAM_CURSOR_NEXT); + if (st == HAM_KEY_NOT_FOUND) { + set_to_nil(kTxn); // TODO Das muss raus! + if (is_nil(kBtree)) + return (st); + couple_to_btree(); + m_last_cmp = -1; + } + if (is_nil(kBtree)) + m_last_cmp = 1; + } + + /* compare keys again */ + if (!is_nil(kBtree) && !m_txn_cursor.is_nil()) + compare(context); + + /* if there's a txn conflict: move next */ + if (st == HAM_TXN_CONFLICT) + return (move_next_key_singlestep(context)); + + /* btree-key is smaller */ + if (m_last_cmp < 0 || m_txn_cursor.is_nil()) { + couple_to_btree(); + update_dupecache(context, kBtree); + return (0); + } + /* txn-key is smaller */ + else if (m_last_cmp > 0 || btrc->get_state() == BtreeCursor::kStateNil) { + couple_to_txnop(); + update_dupecache(context, kTxn); + return (0); + } + /* both keys are equal */ + else { + couple_to_txnop(); + update_dupecache(context, kTxn | kBtree); + return (0); + } +} + +ham_status_t +Cursor::move_next_key(Context *context, uint32_t flags) +{ + ham_status_t st; + + /* are we in the middle of a duplicate list? if yes then move to the + * next duplicate */ + if (get_dupecache_index() > 0 && !(flags & HAM_SKIP_DUPLICATES)) { + st = move_next_dupe(context); + if (st != HAM_LIMITS_REACHED) + return (st); + else if (st == HAM_LIMITS_REACHED && (flags & HAM_ONLY_DUPLICATES)) + return (HAM_KEY_NOT_FOUND); + } + + clear_dupecache(); + + /* either there were no duplicates or we've reached the end of the + * duplicate list. move next till we found a new candidate */ + while (1) { + st = move_next_key_singlestep(context); + if (st) + return (st); + + /* check for duplicates. the dupecache was already updated in + * move_next_key_singlestep() */ + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) { + /* are there any duplicates? if not then they were all erased and + * we move to the previous key */ + if (!has_duplicates()) + continue; + + /* otherwise move to the first duplicate */ + return (move_first_dupe(context)); + } + + /* no duplicates - make sure that we've not coupled to an erased + * item */ + if (is_coupled_to_txnop()) { + if (__txn_cursor_is_erase(&m_txn_cursor)) + continue; + else + return (0); + } + if (is_coupled_to_btree()) { + st = check_if_btree_key_is_erased_or_overwritten(context); + if (st == HAM_KEY_ERASED_IN_TXN) + continue; + else if (st == 0) { + couple_to_txnop(); + return (0); + } + else if (st == HAM_KEY_NOT_FOUND) + return (0); + else + return (st); + } + else + return (HAM_KEY_NOT_FOUND); + } + + ham_assert(!"should never reach this"); + return (HAM_INTERNAL_ERROR); +} + +ham_status_t +Cursor::move_previous_key_singlestep(Context *context) +{ + ham_status_t st = 0; + BtreeCursor *btrc = get_btree_cursor(); + + /* if both cursors point to the same key: move previous with both */ + if (m_last_cmp == 0) { + if (!is_nil(kBtree)) { + st = btrc->move(context, 0, 0, 0, 0, + HAM_CURSOR_PREVIOUS | HAM_SKIP_DUPLICATES); + if (st == HAM_KEY_NOT_FOUND || st == HAM_CURSOR_IS_NIL) { + set_to_nil(kBtree); // TODO muss raus + if (m_txn_cursor.is_nil()) + return (HAM_KEY_NOT_FOUND); + else { + couple_to_txnop(); + m_last_cmp = -1; + } + } + } + if (!m_txn_cursor.is_nil()) { + st = m_txn_cursor.move(HAM_CURSOR_PREVIOUS); + if (st == HAM_KEY_NOT_FOUND || st==HAM_CURSOR_IS_NIL) { + set_to_nil(kTxn); // TODO muss raus + if (is_nil(kBtree)) + return (HAM_KEY_NOT_FOUND); + else { + couple_to_btree(); + m_last_cmp = 1; + } + } + } + } + /* if the btree-key is greater: move previous */ + else if (m_last_cmp > 0) { + st = btrc->move(context, 0, 0, 0, 0, + HAM_CURSOR_PREVIOUS | HAM_SKIP_DUPLICATES); + if (st == HAM_KEY_NOT_FOUND) { + set_to_nil(kBtree); // TODO Das muss raus! + if (m_txn_cursor.is_nil()) + return (st); + couple_to_txnop(); + m_last_cmp = -1; + } + else { + ham_status_t st2 = check_if_btree_key_is_erased_or_overwritten(context); + if (st2 == HAM_TXN_CONFLICT) + st = st2; + } + if (m_txn_cursor.is_nil()) + m_last_cmp = 1; + } + /* if the txn-key is greater OR if both keys are equal: move previous + * with the txn-key (which is chronologically newer) */ + else { + st = m_txn_cursor.move(HAM_CURSOR_PREVIOUS); + if (st == HAM_KEY_NOT_FOUND) { + set_to_nil(kTxn); // TODO Das muss raus! + if (is_nil(kBtree)) + return (st); + couple_to_btree(); + m_last_cmp = 1; + + ham_status_t st2 = check_if_btree_key_is_erased_or_overwritten(context); + if (st2 == HAM_TXN_CONFLICT) + st = st2; + } + if (is_nil(kBtree)) + m_last_cmp = -1; + } + + /* compare keys again */ + if (!is_nil(kBtree) && !m_txn_cursor.is_nil()) + compare(context); + + /* if there's a txn conflict: move previous */ + if (st == HAM_TXN_CONFLICT) + return (move_previous_key_singlestep(context)); + + /* btree-key is greater */ + if (m_last_cmp > 0 || m_txn_cursor.is_nil()) { + couple_to_btree(); + update_dupecache(context, kBtree); + return (0); + } + /* txn-key is greater */ + else if (m_last_cmp < 0 || btrc->get_state() == BtreeCursor::kStateNil) { + couple_to_txnop(); + update_dupecache(context, kTxn); + return (0); + } + /* both keys are equal */ + else { + couple_to_txnop(); + update_dupecache(context, kTxn | kBtree); + return (0); + } +} + +ham_status_t +Cursor::move_previous_key(Context *context, uint32_t flags) +{ + ham_status_t st; + + /* are we in the middle of a duplicate list? if yes then move to the + * previous duplicate */ + if (get_dupecache_index() > 0 && !(flags & HAM_SKIP_DUPLICATES)) { + st = move_previous_dupe(context); + if (st != HAM_LIMITS_REACHED) + return (st); + else if (st == HAM_LIMITS_REACHED && (flags & HAM_ONLY_DUPLICATES)) + return (HAM_KEY_NOT_FOUND); + } + + clear_dupecache(); + + /* either there were no duplicates or we've reached the end of the + * duplicate list. move previous till we found a new candidate */ + while (!is_nil(kBtree) || !m_txn_cursor.is_nil()) { + st = move_previous_key_singlestep(context); + if (st) + return (st); + + /* check for duplicates. the dupecache was already updated in + * move_previous_key_singlestep() */ + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) { + /* are there any duplicates? if not then they were all erased and + * we move to the previous key */ + if (!has_duplicates()) + continue; + + /* otherwise move to the last duplicate */ + return (move_last_dupe(context)); + } + + /* no duplicates - make sure that we've not coupled to an erased + * item */ + if (is_coupled_to_txnop()) { + if (__txn_cursor_is_erase(&m_txn_cursor)) + continue; + else + return (0); + } + if (is_coupled_to_btree()) { + st = check_if_btree_key_is_erased_or_overwritten(context); + if (st == HAM_KEY_ERASED_IN_TXN) + continue; + else if (st == 0) { + couple_to_txnop(); + return (0); + } + else if (st == HAM_KEY_NOT_FOUND) + return (0); + else + return (st); + } + else + return (HAM_KEY_NOT_FOUND); + } + + return (HAM_KEY_NOT_FOUND); +} + +ham_status_t +Cursor::move_first_key_singlestep(Context *context) +{ + ham_status_t btrs, txns; + BtreeCursor *btrc = get_btree_cursor(); + + /* fetch the smallest key from the transaction tree. */ + txns = m_txn_cursor.move(HAM_CURSOR_FIRST); + /* fetch the smallest key from the btree tree. */ + btrs = btrc->move(context, 0, 0, 0, 0, + HAM_CURSOR_FIRST | HAM_SKIP_DUPLICATES); + /* now consolidate - if both trees are empty then return */ + if (btrs == HAM_KEY_NOT_FOUND && txns == HAM_KEY_NOT_FOUND) { + return (HAM_KEY_NOT_FOUND); + } + /* if btree is empty but txn-tree is not: couple to txn */ + else if (btrs == HAM_KEY_NOT_FOUND && txns != HAM_KEY_NOT_FOUND) { + if (txns == HAM_TXN_CONFLICT) + return (txns); + couple_to_txnop(); + update_dupecache(context, kTxn); + return (0); + } + /* if txn-tree is empty but btree is not: couple to btree */ + else if (txns == HAM_KEY_NOT_FOUND && btrs != HAM_KEY_NOT_FOUND) { + couple_to_btree(); + update_dupecache(context, kBtree); + return (0); + } + /* if both trees are not empty then compare them and couple to the + * smaller one */ + else { + ham_assert(btrs == 0 && (txns == 0 + || txns == HAM_KEY_ERASED_IN_TXN + || txns == HAM_TXN_CONFLICT)); + compare(context); + + /* both keys are equal - couple to txn; it's chronologically + * newer */ + if (m_last_cmp == 0) { + if (txns && txns != HAM_KEY_ERASED_IN_TXN) + return (txns); + couple_to_txnop(); + update_dupecache(context, kBtree | kTxn); + } + /* couple to txn */ + else if (m_last_cmp > 0) { + if (txns && txns != HAM_KEY_ERASED_IN_TXN) + return (txns); + couple_to_txnop(); + update_dupecache(context, kTxn); + } + /* couple to btree */ + else { + couple_to_btree(); + update_dupecache(context, kBtree); + } + return (0); + } +} + +ham_status_t +Cursor::move_first_key(Context *context, uint32_t flags) +{ + ham_status_t st = 0; + + /* move to the very very first key */ + st = move_first_key_singlestep(context); + if (st) + return (st); + + /* check for duplicates. the dupecache was already updated in + * move_first_key_singlestep() */ + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) { + /* are there any duplicates? if not then they were all erased and we + * move to the previous key */ + if (!has_duplicates()) + return (move_next_key(context, flags)); + + /* otherwise move to the first duplicate */ + return (move_first_dupe(context)); + } + + /* no duplicates - make sure that we've not coupled to an erased + * item */ + if (is_coupled_to_txnop()) { + if (__txn_cursor_is_erase(&m_txn_cursor)) + return (move_next_key(context, flags)); + else + return (0); + } + if (is_coupled_to_btree()) { + st = check_if_btree_key_is_erased_or_overwritten(context); + if (st == HAM_KEY_ERASED_IN_TXN) + return (move_next_key(context, flags)); + else if (st == 0) { + couple_to_txnop(); + return (0); + } + else if (st == HAM_KEY_NOT_FOUND) + return (0); + else + return (st); + } + else + return (HAM_KEY_NOT_FOUND); +} + +ham_status_t +Cursor::move_last_key_singlestep(Context *context) +{ + ham_status_t btrs, txns; + BtreeCursor *btrc = get_btree_cursor(); + + /* fetch the largest key from the transaction tree. */ + txns = m_txn_cursor.move(HAM_CURSOR_LAST); + /* fetch the largest key from the btree tree. */ + btrs = btrc->move(context, 0, 0, 0, 0, HAM_CURSOR_LAST | HAM_SKIP_DUPLICATES); + /* now consolidate - if both trees are empty then return */ + if (btrs == HAM_KEY_NOT_FOUND && txns == HAM_KEY_NOT_FOUND) { + return (HAM_KEY_NOT_FOUND); + } + /* if btree is empty but txn-tree is not: couple to txn */ + else if (btrs == HAM_KEY_NOT_FOUND && txns != HAM_KEY_NOT_FOUND) { + if (txns == HAM_TXN_CONFLICT) + return (txns); + couple_to_txnop(); + update_dupecache(context, kTxn); + return (0); + } + /* if txn-tree is empty but btree is not: couple to btree */ + else if (txns == HAM_KEY_NOT_FOUND && btrs != HAM_KEY_NOT_FOUND) { + couple_to_btree(); + update_dupecache(context, kBtree); + return (0); + } + /* if both trees are not empty then compare them and couple to the + * greater one */ + else { + ham_assert(btrs == 0 && (txns == 0 + || txns == HAM_KEY_ERASED_IN_TXN + || txns == HAM_TXN_CONFLICT)); + compare(context); + + /* both keys are equal - couple to txn; it's chronologically + * newer */ + if (m_last_cmp == 0) { + if (txns && txns != HAM_KEY_ERASED_IN_TXN) + return (txns); + couple_to_txnop(); + update_dupecache(context, kBtree | kTxn); + } + /* couple to txn */ + else if (m_last_cmp < 1) { + if (txns && txns != HAM_KEY_ERASED_IN_TXN) + return (txns); + couple_to_txnop(); + update_dupecache(context, kTxn); + } + /* couple to btree */ + else { + couple_to_btree(); + update_dupecache(context, kBtree); + } + return (0); + } +} + +ham_status_t +Cursor::move_last_key(Context *context, uint32_t flags) +{ + ham_status_t st = 0; + + /* move to the very very last key */ + st = move_last_key_singlestep(context); + if (st) + return (st); + + /* check for duplicates. the dupecache was already updated in + * move_last_key_singlestep() */ + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) { + /* are there any duplicates? if not then they were all erased and we + * move to the previous key */ + if (!has_duplicates()) + return (move_previous_key(context, flags)); + + /* otherwise move to the last duplicate */ + return (move_last_dupe(context)); + } + + /* no duplicates - make sure that we've not coupled to an erased + * item */ + if (is_coupled_to_txnop()) { + if (__txn_cursor_is_erase(&m_txn_cursor)) + return (move_previous_key(context, flags)); + else + return (0); + } + if (is_coupled_to_btree()) { + st = check_if_btree_key_is_erased_or_overwritten(context); + if (st == HAM_KEY_ERASED_IN_TXN) + return (move_previous_key(context, flags)); + else if (st == 0) { + couple_to_txnop(); + return (0); + } + else if (st == HAM_KEY_NOT_FOUND) + return (0); + else + return (st); + } + else + return (HAM_KEY_NOT_FOUND); +} + +ham_status_t +Cursor::move(Context *context, ham_key_t *key, ham_record_t *record, + uint32_t flags) +{ + ham_status_t st = 0; + bool changed_dir = false; + BtreeCursor *btrc = get_btree_cursor(); + + /* no movement requested? directly retrieve key/record */ + if (!flags) + goto retrieve_key_and_record; + + /* synchronize the btree and transaction cursor if the last operation was + * not a move next/previous OR if the direction changed */ + if ((m_lastop == HAM_CURSOR_PREVIOUS) && (flags & HAM_CURSOR_NEXT)) + changed_dir = true; + else if ((m_lastop == HAM_CURSOR_NEXT) && (flags & HAM_CURSOR_PREVIOUS)) + changed_dir = true; + if (((flags & HAM_CURSOR_NEXT) || (flags & HAM_CURSOR_PREVIOUS)) + && (m_lastop == Cursor::kLookupOrInsert + || changed_dir)) { + if (is_coupled_to_txnop()) + set_to_nil(kBtree); + else + set_to_nil(kTxn); + (void)sync(context, flags, 0); + + if (!m_txn_cursor.is_nil() && !is_nil(kBtree)) + compare(context); + } + + /* we have either skipped duplicates or reached the end of the duplicate + * list. btree cursor and txn cursor are synced and as close to + * each other as possible. Move the cursor in the requested direction. */ + if (flags & HAM_CURSOR_NEXT) { + st = move_next_key(context, flags); + } + else if (flags & HAM_CURSOR_PREVIOUS) { + st = move_previous_key(context, flags); + } + else if (flags & HAM_CURSOR_FIRST) { + clear_dupecache(); + st = move_first_key(context, flags); + } + else { + ham_assert(flags & HAM_CURSOR_LAST); + clear_dupecache(); + st = move_last_key(context, flags); + } + + if (st) + return (st); + +retrieve_key_and_record: + /* retrieve key/record, if requested */ + if (st == 0) { + if (is_coupled_to_txnop()) { +#ifdef HAM_DEBUG + TransactionOperation *op = m_txn_cursor.get_coupled_op(); + ham_assert(!(op->get_flags() & TransactionOperation::kErase)); +#endif + try { + if (key) + m_txn_cursor.copy_coupled_key(key); + if (record) + m_txn_cursor.copy_coupled_record(record); + } + catch (Exception &ex) { + return (ex.code); + } + } + else { + st = btrc->move(context, key, &get_db()->key_arena(get_txn()), + record, &get_db()->record_arena(get_txn()), 0); + } + } + + return (st); +} + +bool +Cursor::is_nil(int what) +{ + switch (what) { + case kBtree: + return (m_btree_cursor.get_state() == BtreeCursor::kStateNil); + case kTxn: + return (m_txn_cursor.is_nil()); + default: + ham_assert(what == 0); + return (m_btree_cursor.get_state() == BtreeCursor::kStateNil + && m_txn_cursor.is_nil()); + } +} + +void +Cursor::set_to_nil(int what) +{ + switch (what) { + case kBtree: + m_btree_cursor.set_to_nil(); + break; + case kTxn: + m_txn_cursor.set_to_nil(); + couple_to_btree(); /* reset flag */ + break; + default: + ham_assert(what == 0); + m_btree_cursor.set_to_nil(); + m_txn_cursor.set_to_nil(); + couple_to_btree(); /* reset flag */ + m_is_first_use = true; + break; + } +} + +uint32_t +Cursor::get_record_count(Context *context, uint32_t flags) +{ + if (is_nil()) + throw Exception(HAM_CURSOR_IS_NIL); + + if (m_txn || is_coupled_to_txnop()) { + if (m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS) { + bool dummy; + sync(context, 0, &dummy); + update_dupecache(context, kTxn | kBtree); + return (m_dupecache.get_count()); + } + else { + /* obviously the key exists, since the cursor is coupled */ + return (1); + } + } + + return (m_btree_cursor.get_record_count(context, flags)); +} + +uint64_t +Cursor::get_record_size(Context *context) +{ + if (is_nil()) + return (HAM_CURSOR_IS_NIL); + + if (is_coupled_to_txnop()) + return (m_txn_cursor.get_record_size()); + else + return (m_btree_cursor.get_record_size(context)); +} + +uint32_t +Cursor::get_duplicate_position() +{ + if (is_nil()) + throw Exception(HAM_CURSOR_IS_NIL); + + // use btree cursor? + if (m_txn_cursor.is_nil()) + return (m_btree_cursor.get_duplicate_index()); + + // otherwise return the index in the duplicate cache + return (get_dupecache_index() - 1); +} + +ham_status_t +Cursor::overwrite(Context *context, Transaction *htxn, + ham_record_t *record, uint32_t flags) +{ + ham_status_t st = 0; + LocalTransaction *txn = dynamic_cast<LocalTransaction *>(htxn); + ham_assert(context->txn == txn); + + /* + * if we're in transactional mode then just append an "insert/OW" operation + * to the txn-tree. + * + * if the txn_cursor is already coupled to a txn-op, then we can use + * txn_cursor_overwrite(). Otherwise we have to call db_insert_txn(). + * + * If transactions are disabled then overwrite the item in the btree. + */ + if (txn) { + if (m_txn_cursor.is_nil() && !(is_nil(0))) { + m_btree_cursor.uncouple_from_page(context); + st = m_db->insert_txn(context, + m_btree_cursor.get_uncoupled_key(), + record, flags | HAM_OVERWRITE, get_txn_cursor()); + } + else { + // TODO also calls db->insert_txn() + st = m_txn_cursor.overwrite(context, txn, record); + } + + if (st == 0) + couple_to_txnop(); + } + else { + m_btree_cursor.overwrite(context, record, flags); + couple_to_btree(); + } + + return (st); +} + +void +Cursor::close() +{ + m_btree_cursor.close(); + m_dupecache.clear(); +} + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.h b/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.h new file mode 100644 index 0000000000..0adf400ab3 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4cursor/cursor.h @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A Cursor is an object which is used to traverse a Database. + * + * A Cursor structure is separated into 3 components: + * 1. The btree cursor + * This cursor can traverse btrees. It is described and implemented + * in btree_cursor.h. + * 2. The txn cursor + * This cursor can traverse txn-trees. It is described and implemented + * in txn_cursor.h. + * 3. The upper layer + * This layer acts as a kind of dispatcher for both cursors. If + * Transactions are used, then it also uses a duplicate cache for + * consolidating the duplicate keys from both cursors. This layer is + * described and implemented in cursor.h (this file). + * + * A Cursor can have several states. It can be + * 1. NIL (not in list) - this is the default state, meaning that the Cursor + * does not point to any key. If the Cursor was initialized, then it's + * "NIL". If the Cursor was erased (i.e. with ham_cursor_erase) then it's + * also "NIL". + * + * relevant functions: + * Cursor::is_nil + * Cursor::set_to_nil + * + * 2. Coupled to the txn-cursor - meaning that the Cursor points to a key + * that is modified in a Transaction. Technically, the txn-cursor points + * to a TransactionOperation structure. + * + * relevant functions: + * Cursor::is_coupled_to_txnop + * Cursor::couple_to_txnop + * + * 3. Coupled to the btree-cursor - meaning that the Cursor points to a key + * that is stored in a Btree. A Btree cursor itself can then be coupled + * (it directly points to a page in the cache) or uncoupled, meaning that + * the page was purged from the cache and has to be fetched from disk when + * the Cursor is used again. This is described in btree_cursor.h. + * + * relevant functions: + * Cursor::is_coupled_to_btree + * Cursor::couple_to_btree + * + * The dupecache is used when information from the btree and the txn-tree + * is merged. The btree cursor has its private dupecache. The dupecache + * increases performance (and complexity). + * + * The cursor interface is used in db_local.cc. Many of the functions use + * a high-level cursor interface (i.e. @ref cursor_create, @ref cursor_clone) + * while some directly use the low-level interfaces of btree_cursor.h and + * txn_cursor.h. Over time i will clean this up, trying to maintain a clear + * separation of the 3 layers, and only accessing the top-level layer in + * cursor.h. This is work in progress. + * + * In order to speed up Cursor::move() we keep track of the last compare + * between the two cursors. i.e. if the btree cursor is currently pointing to + * a larger key than the txn-cursor, the 'lastcmp' field is <0 etc. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_CURSORS_H +#define HAM_CURSORS_H + +#include "0root/root.h" + +#include <vector> + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "4txn/txn_cursor.h" +#include "3btree/btree_cursor.h" +#include "3blob_manager/blob_manager.h" +#include "4db/db_local.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +// A helper structure; ham_cursor_t is declared in ham/hamsterdb.h as an +// opaque C structure, but internally we use a C++ class. The ham_cursor_t +// struct satisfies the C compiler, and internally we just cast the pointers. +struct ham_cursor_t +{ + bool _dummy; +}; + +namespace hamsterdb { + +struct Context; + +// A single line in the dupecache structure - can reference a btree +// record or a txn-op +class DupeCacheLine +{ + public: + DupeCacheLine(bool use_btree = true, uint64_t btree_dupeidx = 0) + : m_btree_dupeidx(btree_dupeidx), m_op(0), m_use_btree(use_btree) { + ham_assert(use_btree == true); + } + + DupeCacheLine(bool use_btree, TransactionOperation *op) + : m_btree_dupeidx(0), m_op(op), m_use_btree(use_btree) { + ham_assert(use_btree == false); + } + + // Returns true if this cache entry is a duplicate in the btree index + // (otherwise it's a duplicate in the transaction index) + bool use_btree() const { + return (m_use_btree); + } + + // Returns the btree duplicate index + uint64_t get_btree_dupe_idx() { + ham_assert(m_use_btree == true); + return (m_btree_dupeidx); + } + + // Sets the btree duplicate index + void set_btree_dupe_idx(uint64_t idx) { + m_use_btree = true; + m_btree_dupeidx = idx; + m_op = 0; + } + + // Returns the txn-op duplicate + TransactionOperation *get_txn_op() { + ham_assert(m_use_btree == false); + return (m_op); + } + + // Sets the txn-op duplicate + void set_txn_op(TransactionOperation *op) { + m_use_btree = false; + m_op = op; + m_btree_dupeidx = 0; + } + + private: + // The btree duplicate index (of the original btree dupe table) + uint64_t m_btree_dupeidx; + + // The txn op structure that we refer to + TransactionOperation *m_op; + + // using btree or txn duplicates? + bool m_use_btree; +}; + +// +// The dupecache is a cache for duplicate keys +// +class DupeCache { + public: + // default constructor - creates an empty dupecache with room for 8 + // duplicates + DupeCache() { + m_elements.reserve(8); + } + + // Returns the number of elements in the cache + uint32_t get_count() const { + return ((uint32_t)m_elements.size()); + } + + // Returns an element from the cache + DupeCacheLine *get_element(unsigned idx) { + return (&m_elements[idx]); + } + + // Returns a pointer to the first element from the cache + DupeCacheLine *get_first_element() { + return (&m_elements[0]); + } + + // Clones this dupe-cache into 'other' + void clone(DupeCache *other) { + other->m_elements = m_elements; + } + + // Inserts a new item somewhere in the cache; resizes the + // cache if necessary + void insert(unsigned position, const DupeCacheLine &dcl) { + m_elements.insert(m_elements.begin() + position, dcl); + } + + // Append an element to the dupecache + void append(const DupeCacheLine &dcl) { + m_elements.push_back(dcl); + } + + // Erases an item + void erase(uint32_t position) { + m_elements.erase(m_elements.begin() + position); + } + + // Clears the cache; frees all resources + void clear() { + m_elements.resize(0); + } + + private: + // The cached elements + std::vector<DupeCacheLine> m_elements; +}; + + +// +// the Database Cursor +// +class Cursor +{ + public: + // The flags have ranges: + // 0 - 0x1000000-1: btree_cursor + // > 0x1000000: cursor + enum { + // Flags for set_to_nil, is_nil + kBoth = 0, + kBtree = 1, + kTxn = 2, + + // Flag for sync(): do not use approx matching if the key + // is not available + kSyncOnlyEqualKeys = 0x200000, + + // Flag for sync(): do not load the key if there's an approx. + // match. Only positions the cursor. + kSyncDontLoadKey = 0x100000, + + // Cursor flag: cursor is coupled to the txn-cursor + kCoupledToTxn = 0x1000000, + + // Flag for set_lastop() + kLookupOrInsert = 0x10000 + }; + + public: + // Constructor; retrieves pointer to db and txn, initializes all members + Cursor(LocalDatabase *db, Transaction *txn = 0, uint32_t flags = 0); + + // Copy constructor; used for cloning a Cursor + Cursor(Cursor &other); + + // Destructor; sets cursor to nil + ~Cursor() { + set_to_nil(); + } + + // Returns the Database + LocalDatabase *get_db() { + return (m_db); + } + + // Returns the Transaction handle + Transaction *get_txn() { + return (m_txn); + } + + // Sets the Transaction handle; often used to assign a temporary + // Transaction to this cursor + void set_txn(Transaction *txn) { + m_txn = txn; + } + + // Sets the cursor to nil + void set_to_nil(int what = kBoth); + + // Returns true if a cursor is nil (Not In List - does not point to any + // key) + // |what| is one of the flags kBoth, kTxn, kBtree + bool is_nil(int what = kBoth); + + // Couples the cursor to the btree key + void couple_to_btree() { + m_flags &= ~kCoupledToTxn; + } + + // Returns true if a cursor is coupled to the btree + bool is_coupled_to_btree() const { + return (!(m_flags & kCoupledToTxn)); + } + + // Couples the cursor to the txn-op + void couple_to_txnop() { + m_flags |= kCoupledToTxn; + } + + // Returns true if a cursor is coupled to a txn-op + bool is_coupled_to_txnop() const { + return ((m_flags & kCoupledToTxn) ? true : false); + } + + // Retrieves the number of duplicates of the current key + uint32_t get_record_count(Context *context, uint32_t flags); + + // Retrieves the duplicate position of a cursor + uint32_t get_duplicate_position(); + + // Retrieves the size of the current record + uint64_t get_record_size(Context *context); + + // Overwrites the record of the current key + // + // The Transaction is passed as a separate pointer since it might be a + // local/temporary Transaction that was created only for this single + // operation. + ham_status_t overwrite(Context *context, Transaction *txn, + ham_record_t *record, uint32_t flags); + + // Moves a Cursor (ham_cursor_move) + ham_status_t move(Context *context, ham_key_t *key, ham_record_t *record, + uint32_t flags); + + // Closes an existing cursor (ham_cursor_close) + void close(); + + // Updates (or builds) the dupecache for a cursor + // + // The |what| parameter specifies if the dupecache is initialized from + // btree (kBtree), from txn (kTxn) or both. + void update_dupecache(Context *context, uint32_t what); + + // Appends the duplicates of the BtreeCursor to the duplicate cache. + void append_btree_duplicates(Context *context, BtreeCursor *btc, + DupeCache *dc); + + // Clears the dupecache and disconnect the Cursor from any duplicate key + void clear_dupecache() { + m_dupecache.clear(); + set_dupecache_index(0); + } + + // Couples the cursor to a duplicate in the dupe table + // dupe_id is a 1 based index!! + void couple_to_dupe(uint32_t dupe_id); + + // Synchronizes txn- and btree-cursor + // + // If txn-cursor is nil then try to move the txn-cursor to the same key + // as the btree cursor. + // If btree-cursor is nil then try to move the btree-cursor to the same key + // as the txn cursor. + // If both are nil, or both are valid, then nothing happens + // + // |equal_key| is set to true if the keys in both cursors are equal. + void sync(Context *context, uint32_t flags, bool *equal_keys); + + // Returns the number of duplicates in the duplicate cache + // The duplicate cache is updated if necessary + uint32_t get_dupecache_count(Context *context) { + if (!(m_db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS)) + return (0); + + TransactionCursor *txnc = get_txn_cursor(); + if (txnc->get_coupled_op()) + update_dupecache(context, kBtree | kTxn); + else + update_dupecache(context, kBtree); + return (m_dupecache.get_count()); + } + + // Get the 'next' Cursor in this Database + Cursor *get_next() { + return (m_next); + } + + // Set the 'next' Cursor in this Database + void set_next(Cursor *next) { + m_next = next; + } + + // Get the 'previous' Cursor in this Database + Cursor *get_previous() { + return (m_previous); + } + + // Set the 'previous' Cursor in this Database + void set_previous(Cursor *previous) { + m_previous = previous; + } + + // Returns the Transaction cursor + // TODO required? + TransactionCursor *get_txn_cursor() { + return (&m_txn_cursor); + } + + // Returns the Btree cursor + // TODO required? + BtreeCursor *get_btree_cursor() { + return (&m_btree_cursor); + } + + // Returns the remote Cursor handle + uint64_t get_remote_handle() { + return (m_remote_handle); + } + + // Returns the remote Cursor handle + void set_remote_handle(uint64_t handle) { + m_remote_handle = handle; + } + + // Returns a pointer to the duplicate cache + // TODO really required? + DupeCache *get_dupecache() { + return (&m_dupecache); + } + + // Returns a pointer to the duplicate cache + // TODO really required? + const DupeCache *get_dupecache() const { + return (&m_dupecache); + } + + // Returns the current index in the dupe cache + uint32_t get_dupecache_index() const { + return (m_dupecache_index); + } + + // Sets the current index in the dupe cache + void set_dupecache_index(uint32_t index) { + m_dupecache_index = index; + } + + // Returns true if this cursor was never used before + // TODO this is identical to is_nil()?? + bool is_first_use() const { + return (m_is_first_use); + } + + // Stores the current operation; needed for ham_cursor_move + // TODO should be private + void set_lastop(uint32_t lastop) { + m_lastop = lastop; + m_is_first_use = false; + } + + private: + // Checks if a btree cursor points to a key that was overwritten or erased + // in the txn-cursor + // + // This is needed when moving the cursor backwards/forwards + // and consolidating the btree and the txn-tree + ham_status_t check_if_btree_key_is_erased_or_overwritten(Context *context); + + // Compares btree and txn-cursor; stores result in lastcmp + int compare(Context *context); + + // Returns true if this key has duplicates + bool has_duplicates() const { + return (m_dupecache.get_count() > 0); + } + + // Moves cursor to the first duplicate + ham_status_t move_first_dupe(Context *context); + + // Moves cursor to the last duplicate + ham_status_t move_last_dupe(Context *context); + + // Moves cursor to the next duplicate + ham_status_t move_next_dupe(Context *context); + + // Moves cursor to the previous duplicate + ham_status_t move_previous_dupe(Context *context); + + // Moves cursor to the first key + ham_status_t move_first_key(Context *context, uint32_t flags); + + // Moves cursor to the last key + ham_status_t move_last_key(Context *context, uint32_t flags); + + // Moves cursor to the next key + ham_status_t move_next_key(Context *context, uint32_t flags); + + // Moves cursor to the previous key + ham_status_t move_previous_key(Context *context, uint32_t flags); + + // Moves cursor to the first key - helper function + ham_status_t move_first_key_singlestep(Context *context); + + // Moves cursor to the last key - helper function + ham_status_t move_last_key_singlestep(Context *context); + + // Moves cursor to the next key - helper function + ham_status_t move_next_key_singlestep(Context *context); + + // Moves cursor to the previous key - helper function + ham_status_t move_previous_key_singlestep(Context *context); + + // Pointer to the Database object + LocalDatabase *m_db; + + // Pointer to the Transaction + Transaction *m_txn; + + // A Cursor which can walk over Transaction trees + TransactionCursor m_txn_cursor; + + // A Cursor which can walk over B+trees + BtreeCursor m_btree_cursor; + + // The remote database handle + uint64_t m_remote_handle; + + // Linked list of all Cursors in this Database + Cursor *m_next, *m_previous; + + // A cache for all duplicates of the current key. needed for + // ham_cursor_move, ham_find and other functions. The cache is + // used to consolidate all duplicates of btree and txn. + DupeCache m_dupecache; + + /** The current position of the cursor in the cache. This is a + * 1-based index. 0 means that the cache is not in use. */ + uint32_t m_dupecache_index; + + // The last operation (insert/find or move); needed for + // ham_cursor_move. Values can be HAM_CURSOR_NEXT, + // HAM_CURSOR_PREVIOUS or CURSOR_LOOKUP_INSERT + uint32_t m_lastop; + + // The result of the last compare operation + int m_last_cmp; + + // Cursor flags + uint32_t m_flags; + + // true if this cursor was never used + bool m_is_first_use; +}; + +} // namespace hamsterdb + +#endif /* HAM_CURSORS_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db.cc b/plugins/Dbx_kv/src/hamsterdb/src/4db/db.cc new file mode 100644 index 0000000000..7d6cd82929 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db.cc @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4db/db.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +Database::Database(Environment *env, DatabaseConfiguration &config) + : m_env(env), m_config(config), m_error(0), m_context(0), m_cursor_list(0) +{ +} + +ham_status_t +Database::cursor_create(Cursor **pcursor, Transaction *txn, uint32_t flags) +{ + try { + Cursor *cursor = cursor_create_impl(txn, flags); + + /* fix the linked list of cursors */ + cursor->set_next(m_cursor_list); + if (m_cursor_list) + m_cursor_list->set_previous(cursor); + m_cursor_list = cursor; + + if (txn) + txn->increase_cursor_refcount(); + + *pcursor = cursor; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Database::cursor_clone(Cursor **pdest, Cursor *src) +{ + try { + Cursor *dest = cursor_clone_impl(src); + + // fix the linked list of cursors + dest->set_previous(0); + dest->set_next(m_cursor_list); + ham_assert(m_cursor_list != 0); + m_cursor_list->set_previous(dest); + m_cursor_list = dest; + + // initialize the remaining fields + if (src->get_txn()) + src->get_txn()->increase_cursor_refcount(); + + *pdest = dest; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Database::cursor_close(Cursor *cursor) +{ + try { + Cursor *p, *n; + + // first close the cursor + cursor_close_impl(cursor); + + // decrease the transaction refcount; the refcount specifies how many + // cursors are attached to the transaction + if (cursor->get_txn()) + cursor->get_txn()->decrease_cursor_refcount(); + + // fix the linked list of cursors + p = cursor->get_previous(); + n = cursor->get_next(); + + if (p) + p->set_next(n); + else + m_cursor_list = n; + + if (n) + n->set_previous(p); + + cursor->set_next(0); + cursor->set_previous(0); + + delete cursor; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +// No need to catch Exceptions - they're caught in Environment::close_db +ham_status_t +Database::close(uint32_t flags) +{ + // auto-cleanup cursors? + if (flags & HAM_AUTO_CLEANUP) { + Cursor *cursor; + while ((cursor = m_cursor_list)) + cursor_close(cursor); + } + else if (m_cursor_list) { + ham_trace(("cannot close Database if Cursors are still open")); + return (set_error(HAM_CURSOR_STILL_OPEN)); + } + + // the derived classes can now do the bulk of the work + ham_status_t st = close_impl(flags); + if (st) + return (set_error(st)); + + m_env = 0; + return (0); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db.h b/plugins/Dbx_kv/src/hamsterdb/src/4db/db.h new file mode 100644 index 0000000000..0290cc86b0 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: no + */ + +#ifndef HAM_DB_H +#define HAM_DB_H + +#include "0root/root.h" + +#include "ham/hamsterdb_int.h" +#include "ham/hamsterdb_ola.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "2config/db_config.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +// A helper structure; ham_db_t is declared in ham/hamsterdb.h as an +// opaque C structure, but internally we use a C++ class. The ham_db_t +// struct satisfies the C compiler, and internally we just cast the pointers. +struct ham_db_t { + int dummy; +}; + +namespace hamsterdb { + +class Cursor; +struct ScanVisitor; + +/* + * An abstract base class for a Database; is overwritten for local and + * remote implementations + */ +class Database +{ + public: + // Constructor + Database(Environment *env, DatabaseConfiguration &config); + + virtual ~Database() { + } + + // Returns the Environment pointer + Environment *get_env() { + return (m_env); + } + + // Returns the Database's configuration + const DatabaseConfiguration &config() const { + return (m_config); + } + + // Returns the runtime-flags - the flags are "mixed" with the flags from + // the Environment + uint32_t get_flags() { + return (m_env->get_flags() | m_config.flags); + } + + // Returns the database name + uint16_t name() const { + return (m_config.db_name); + } + + // Sets the database name + void set_name(uint16_t name) { + m_config.db_name = name; + } + + // Fills in the current metrics + virtual void fill_metrics(ham_env_metrics_t *metrics) = 0; + + // Returns Database parameters (ham_db_get_parameters) + virtual ham_status_t get_parameters(ham_parameter_t *param) = 0; + + // Checks Database integrity (ham_db_check_integrity) + virtual ham_status_t check_integrity(uint32_t flags) = 0; + + // Returns the number of keys (ham_db_get_key_count) + virtual ham_status_t count(Transaction *txn, bool distinct, + uint64_t *pcount) = 0; + + // Scans the whole database, applies a processor function + virtual ham_status_t scan(Transaction *txn, ScanVisitor *visitor, + bool distinct) = 0; + + // Inserts a key/value pair (ham_db_insert, ham_cursor_insert) + virtual ham_status_t insert(Cursor *cursor, Transaction *txn, + ham_key_t *key, ham_record_t *record, uint32_t flags) = 0; + + // Erase a key/value pair (ham_db_erase, ham_cursor_erase) + virtual ham_status_t erase(Cursor *cursor, Transaction *txn, ham_key_t *key, + uint32_t flags) = 0; + + // Lookup of a key/value pair (ham_db_find, ham_cursor_find) + virtual ham_status_t find(Cursor *cursor, Transaction *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags) = 0; + + // Creates a cursor (ham_cursor_create) + virtual ham_status_t cursor_create(Cursor **pcursor, Transaction *txn, + uint32_t flags); + + // Clones a cursor (ham_cursor_clone) + virtual ham_status_t cursor_clone(Cursor **pdest, Cursor *src); + + // Returns number of duplicates (ham_cursor_get_record_count) + virtual ham_status_t cursor_get_record_count(Cursor *cursor, + uint32_t flags, uint32_t *pcount) = 0; + + // Returns position in duplicate list (ham_cursor_get_duplicate_position) + virtual ham_status_t cursor_get_duplicate_position(Cursor *cursor, + uint32_t *pposition) = 0; + + // Get current record size (ham_cursor_get_record_size) + virtual ham_status_t cursor_get_record_size(Cursor *cursor, + uint64_t *psize) = 0; + + // Overwrites the record of a cursor (ham_cursor_overwrite) + virtual ham_status_t cursor_overwrite(Cursor *cursor, + ham_record_t *record, uint32_t flags) = 0; + + // Moves a cursor, returns key and/or record (ham_cursor_move) + virtual ham_status_t cursor_move(Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags) = 0; + + // Closes a cursor (ham_cursor_close) + ham_status_t cursor_close(Cursor *cursor); + + // Closes the Database (ham_db_close) + ham_status_t close(uint32_t flags); + + // Returns the last error code + ham_status_t get_error() const { + return (m_error); + } + + // Sets the last error code + ham_status_t set_error(ham_status_t e) { + return ((m_error = e)); + } + + // Returns the user-provided context pointer (ham_get_context_data) + void *get_context_data() { + return (m_context); + } + + // Sets the user-provided context pointer (ham_set_context_data) + void set_context_data(void *ctxt) { + m_context = ctxt; + } + + // Returns the head of the linked list with all cursors + Cursor *cursor_list() { + return (m_cursor_list); + } + + // Returns the memory buffer for the key data: the per-database buffer + // if |txn| is null or temporary, otherwise the buffer from the |txn| + ByteArray &key_arena(Transaction *txn) { + return ((txn == 0 || (txn->get_flags() & HAM_TXN_TEMPORARY)) + ? m_key_arena + : txn->key_arena()); + } + + // Returns the memory buffer for the record data: the per-database buffer + // if |txn| is null or temporary, otherwise the buffer from the |txn| + ByteArray &record_arena(Transaction *txn) { + return ((txn == 0 || (txn->get_flags() & HAM_TXN_TEMPORARY)) + ? m_record_arena + : txn->record_arena()); + } + + protected: + // Creates a cursor; this is the actual implementation + virtual Cursor *cursor_create_impl(Transaction *txn, uint32_t flags) = 0; + + // Clones a cursor; this is the actual implementation + virtual Cursor *cursor_clone_impl(Cursor *src) = 0; + + // Closes a cursor; this is the actual implementation + virtual void cursor_close_impl(Cursor *c) = 0; + + // Closes a database; this is the actual implementation + virtual ham_status_t close_impl(uint32_t flags) = 0; + + // the current Environment + Environment *m_env; + + // the configuration settings + DatabaseConfiguration m_config; + + // the last error code + ham_status_t m_error; + + // the user-provided context data + void *m_context; + + // linked list of all cursors + Cursor *m_cursor_list; + + // This is where key->data points to when returning a + // key to the user; used if Transactions are disabled + ByteArray m_key_arena; + + // This is where record->data points to when returning a + // record to the user; used if Transactions are disabled + ByteArray m_record_arena; +}; + +} // namespace hamsterdb + +#endif /* HAM_DB_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.cc b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.cc new file mode 100644 index 0000000000..849eb4e7aa --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.cc @@ -0,0 +1,1776 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <boost/scope_exit.hpp> + +// Always verify that a file of level N does not include headers > N! +#include "1mem/mem.h" +#include "1os/os.h" +#include "2page/page.h" +#include "2device/device.h" +#include "3page_manager/page_manager.h" +#include "3journal/journal.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_index_factory.h" +#include "3btree/btree_cursor.h" +#include "3btree/btree_stats.h" +#include "4db/db_local.h" +#include "4context/context.h" +#include "4cursor/cursor.h" +#include "4txn/txn_local.h" +#include "4txn/txn_cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +ham_status_t +LocalDatabase::check_insert_conflicts(Context *context, TransactionNode *node, + ham_key_t *key, uint32_t flags) +{ + TransactionOperation *op = 0; + + /* + * pick the tree_node of this key, and walk through each operation + * in reverse chronological order (from newest to oldest): + * - is this op part of an aborted txn? then skip it + * - is this op part of a committed txn? then look at the + * operation in detail + * - is this op part of an txn which is still active? return an error + * because we've found a conflict + * - if a committed txn has erased the item then there's no need + * to continue checking older, committed txns + */ + op = node->get_newest_op(); + while (op) { + LocalTransaction *optxn = op->get_txn(); + if (optxn->is_aborted()) + ; /* nop */ + else if (optxn->is_committed() || context->txn == optxn) { + /* if key was erased then it doesn't exist and can be + * inserted without problems */ + if (op->get_flags() & TransactionOperation::kIsFlushed) + ; /* nop */ + else if (op->get_flags() & TransactionOperation::kErase) + return (0); + /* if the key already exists then we can only continue if + * we're allowed to overwrite it or to insert a duplicate */ + else if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite) + || (op->get_flags() & TransactionOperation::kInsertDuplicate)) { + if ((flags & HAM_OVERWRITE) || (flags & HAM_DUPLICATE)) + return (0); + else + return (HAM_DUPLICATE_KEY); + } + else if (!(op->get_flags() & TransactionOperation::kNop)) { + ham_assert(!"shouldn't be here"); + return (HAM_DUPLICATE_KEY); + } + } + else { /* txn is still active */ + return (HAM_TXN_CONFLICT); + } + + op = op->get_previous_in_node(); + } + + /* + * we've successfully checked all un-flushed transactions and there + * were no conflicts. Now check all transactions which are already + * flushed - basically that's identical to a btree lookup. + * + * however we can skip this check if we do not care about duplicates. + */ + if ((flags & HAM_OVERWRITE) + || (flags & HAM_DUPLICATE) + || (get_flags() & (HAM_RECORD_NUMBER32 | HAM_RECORD_NUMBER64))) + return (0); + + ham_status_t st = m_btree_index->find(context, 0, key, 0, 0, 0, flags); + switch (st) { + case HAM_KEY_NOT_FOUND: + return (0); + case HAM_SUCCESS: + return (HAM_DUPLICATE_KEY); + default: + return (st); + } +} + +ham_status_t +LocalDatabase::check_erase_conflicts(Context *context, TransactionNode *node, + ham_key_t *key, uint32_t flags) +{ + TransactionOperation *op = 0; + + /* + * pick the tree_node of this key, and walk through each operation + * in reverse chronological order (from newest to oldest): + * - is this op part of an aborted txn? then skip it + * - is this op part of a committed txn? then look at the + * operation in detail + * - is this op part of an txn which is still active? return an error + * because we've found a conflict + * - if a committed txn has erased the item then there's no need + * to continue checking older, committed txns + */ + op = node->get_newest_op(); + while (op) { + Transaction *optxn = op->get_txn(); + if (optxn->is_aborted()) + ; /* nop */ + else if (optxn->is_committed() || context->txn == optxn) { + if (op->get_flags() & TransactionOperation::kIsFlushed) + ; /* nop */ + /* if key was erased then it doesn't exist and we fail with + * an error */ + else if (op->get_flags() & TransactionOperation::kErase) + return (HAM_KEY_NOT_FOUND); + /* if the key exists then we're successful */ + else if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite) + || (op->get_flags() & TransactionOperation::kInsertDuplicate)) { + return (0); + } + else if (!(op->get_flags() & TransactionOperation::kNop)) { + ham_assert(!"shouldn't be here"); + return (HAM_KEY_NOT_FOUND); + } + } + else { /* txn is still active */ + return (HAM_TXN_CONFLICT); + } + + op = op->get_previous_in_node(); + } + + /* + * we've successfully checked all un-flushed transactions and there + * were no conflicts. Now check all transactions which are already + * flushed - basically that's identical to a btree lookup. + */ + return (m_btree_index->find(context, 0, key, 0, 0, 0, flags)); +} + +ham_status_t +LocalDatabase::insert_txn(Context *context, ham_key_t *key, + ham_record_t *record, uint32_t flags, TransactionCursor *cursor) +{ + ham_status_t st = 0; + TransactionOperation *op; + bool node_created = false; + + /* get (or create) the node for this key */ + TransactionNode *node = m_txn_index->get(key, 0); + if (!node) { + node = new TransactionNode(this, key); + node_created = true; + // TODO only store when the operation is successful? + m_txn_index->store(node); + } + + // check for conflicts of this key + // + // !! + // afterwards, clear the changeset; check_insert_conflicts() + // checks if a key already exists, and this fills the changeset + st = check_insert_conflicts(context, node, key, flags); + if (st) { + if (node_created) { + m_txn_index->remove(node); + delete node; + } + return (st); + } + + // append a new operation to this node + op = node->append(context->txn, flags, + (flags & HAM_PARTIAL) | + ((flags & HAM_DUPLICATE) + ? TransactionOperation::kInsertDuplicate + : (flags & HAM_OVERWRITE) + ? TransactionOperation::kInsertOverwrite + : TransactionOperation::kInsert), + lenv()->next_lsn(), key, record); + + // if there's a cursor then couple it to the op; also store the + // dupecache-index in the op (it's needed for DUPLICATE_INSERT_BEFORE/NEXT) */ + if (cursor) { + Cursor *c = cursor->get_parent(); + if (c->get_dupecache_index()) + op->set_referenced_dupe(c->get_dupecache_index()); + + cursor->couple_to_op(op); + + // all other cursors need to increment their dupe index, if their + // index is > this cursor's index + increment_dupe_index(context, node, c, c->get_dupecache_index()); + } + + // append journal entry + if (m_env->get_flags() & HAM_ENABLE_RECOVERY + && m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) { + Journal *j = lenv()->journal(); + j->append_insert(this, context->txn, key, record, + flags & HAM_DUPLICATE ? flags : flags | HAM_OVERWRITE, + op->get_lsn()); + } + + ham_assert(st == 0); + return (0); +} + +ham_status_t +LocalDatabase::find_txn(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags) +{ + ham_status_t st = 0; + TransactionOperation *op = 0; + bool first_loop = true; + bool exact_is_erased = false; + + ByteArray *pkey_arena = &key_arena(context->txn); + ByteArray *precord_arena = &record_arena(context->txn); + + ham_key_set_intflags(key, + (ham_key_get_intflags(key) & (~BtreeKey::kApproximate))); + + /* get the node for this key (but don't create a new one if it does + * not yet exist) */ + TransactionNode *node = m_txn_index->get(key, flags); + + /* + * pick the node of this key, and walk through each operation + * in reverse chronological order (from newest to oldest): + * - is this op part of an aborted txn? then skip it + * - is this op part of a committed txn? then look at the + * operation in detail + * - is this op part of an txn which is still active? return an error + * because we've found a conflict + * - if a committed txn has erased the item then there's no need + * to continue checking older, committed txns + */ +retry: + if (node) + op = node->get_newest_op(); + while (op) { + Transaction *optxn = op->get_txn(); + if (optxn->is_aborted()) + ; /* nop */ + else if (optxn->is_committed() || context->txn == optxn) { + if (op->get_flags() & TransactionOperation::kIsFlushed) + ; /* nop */ + /* if key was erased then it doesn't exist and we can return + * immediately + * + * if an approximate match is requested then move to the next + * or previous node + */ + else if (op->get_flags() & TransactionOperation::kErase) { + if (first_loop + && !(ham_key_get_intflags(key) & BtreeKey::kApproximate)) + exact_is_erased = true; + first_loop = false; + if (flags & HAM_FIND_LT_MATCH) { + node = node->get_previous_sibling(); + if (!node) + break; + ham_key_set_intflags(key, + (ham_key_get_intflags(key) | BtreeKey::kApproximate)); + goto retry; + } + else if (flags & HAM_FIND_GT_MATCH) { + node = node->get_next_sibling(); + if (!node) + break; + ham_key_set_intflags(key, + (ham_key_get_intflags(key) | BtreeKey::kApproximate)); + goto retry; + } + /* if a duplicate was deleted then check if there are other duplicates + * left */ + st = HAM_KEY_NOT_FOUND; + // TODO merge both calls + if (cursor) { + cursor->get_txn_cursor()->couple_to_op(op); + cursor->couple_to_txnop(); + } + if (op->get_referenced_dupe() > 1) { + // not the first dupe - there are other dupes + st = 0; + } + else if (op->get_referenced_dupe() == 1) { + // check if there are other dupes + bool is_equal; + (void)cursor->sync(context, Cursor::kSyncOnlyEqualKeys, &is_equal); + if (!is_equal) // TODO merge w/ line above? + cursor->set_to_nil(Cursor::kBtree); + st = cursor->get_dupecache_count(context) ? 0 : HAM_KEY_NOT_FOUND; + } + return (st); + } + /* if the key already exists then return its record; do not + * return pointers to TransactionOperation::get_record, because it may be + * flushed and the user's pointers would be invalid */ + else if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite) + || (op->get_flags() & TransactionOperation::kInsertDuplicate)) { + if (cursor) { // TODO merge those calls + cursor->get_txn_cursor()->couple_to_op(op); + cursor->couple_to_txnop(); + } + // approx match? leave the loop and continue + // with the btree + if (ham_key_get_intflags(key) & BtreeKey::kApproximate) + break; + // otherwise copy the record and return + if (record) + return (LocalDatabase::copy_record(this, context->txn, op, record)); + return (0); + } + else if (!(op->get_flags() & TransactionOperation::kNop)) { + ham_assert(!"shouldn't be here"); + return (HAM_KEY_NOT_FOUND); + } + } + else { /* txn is still active */ + return (HAM_TXN_CONFLICT); + } + + op = op->get_previous_in_node(); + } + + /* + * if there was an approximate match: check if the btree provides + * a better match + * + * TODO use alloca or ByteArray instead of Memory::allocate() + */ + if (op && ham_key_get_intflags(key) & BtreeKey::kApproximate) { + ham_key_t txnkey = {0}; + ham_key_t *k = op->get_node()->get_key(); + txnkey.size = k->size; + txnkey._flags = BtreeKey::kApproximate; + txnkey.data = Memory::allocate<uint8_t>(txnkey.size); + memcpy(txnkey.data, k->data, txnkey.size); + + ham_key_set_intflags(key, 0); + + // the "exact match" key was erased? then don't fetch it again + if (exact_is_erased) + flags = flags & (~HAM_FIND_EXACT_MATCH); + + // now lookup in the btree + if (cursor) + cursor->set_to_nil(Cursor::kBtree); + st = m_btree_index->find(context, cursor, key, pkey_arena, record, + precord_arena, flags); + if (st == HAM_KEY_NOT_FOUND) { + if (!(key->flags & HAM_KEY_USER_ALLOC) && txnkey.data) { + pkey_arena->resize(txnkey.size); + key->data = pkey_arena->get_ptr(); + } + if (txnkey.data) { + ::memcpy(key->data, txnkey.data, txnkey.size); + Memory::release(txnkey.data); + } + key->size = txnkey.size; + key->_flags = txnkey._flags; + + if (cursor) { // TODO merge those calls + cursor->get_txn_cursor()->couple_to_op(op); + cursor->couple_to_txnop(); + } + if (record) + return (LocalDatabase::copy_record(this, context->txn, op, record)); + return (0); + } + else if (st) + return (st); + // the btree key is a direct match? then return it + if ((!(ham_key_get_intflags(key) & BtreeKey::kApproximate)) + && (flags & HAM_FIND_EXACT_MATCH)) { + Memory::release(txnkey.data); + if (cursor) + cursor->couple_to_btree(); + return (0); + } + // if there's an approx match in the btree: compare both keys and + // use the one that is closer. if the btree is closer: make sure + // that it was not erased or overwritten in a transaction + int cmp = m_btree_index->compare_keys(key, &txnkey); + bool use_btree = false; + if (flags & HAM_FIND_GT_MATCH) { + if (cmp < 0) + use_btree = true; + } + else if (flags & HAM_FIND_LT_MATCH) { + if (cmp > 0) + use_btree = true; + } + else + ham_assert(!"shouldn't be here"); + + if (use_btree) { + Memory::release(txnkey.data); + // lookup again, with the same flags and the btree key. + // this will check if the key was erased or overwritten + // in a transaction + st = find_txn(context, cursor, key, record, flags | HAM_FIND_EXACT_MATCH); + if (st == 0) + ham_key_set_intflags(key, + (ham_key_get_intflags(key) | BtreeKey::kApproximate)); + return (st); + } + else { // use txn + if (!(key->flags & HAM_KEY_USER_ALLOC) && txnkey.data) { + pkey_arena->resize(txnkey.size); + key->data = pkey_arena->get_ptr(); + } + if (txnkey.data) { + ::memcpy(key->data, txnkey.data, txnkey.size); + Memory::release(txnkey.data); + } + key->size = txnkey.size; + key->_flags = txnkey._flags; + + if (cursor) { // TODO merge those calls + cursor->get_txn_cursor()->couple_to_op(op); + cursor->couple_to_txnop(); + } + if (record) + return (LocalDatabase::copy_record(this, context->txn, op, record)); + return (0); + } + } + + /* + * no approximate match: + * + * we've successfully checked all un-flushed transactions and there + * were no conflicts, and we have not found the key: now try to + * lookup the key in the btree. + */ + return (m_btree_index->find(context, cursor, key, pkey_arena, record, + precord_arena, flags)); +} + +ham_status_t +LocalDatabase::erase_txn(Context *context, ham_key_t *key, uint32_t flags, + TransactionCursor *cursor) +{ + ham_status_t st = 0; + TransactionOperation *op; + bool node_created = false; + Cursor *pc = 0; + if (cursor) + pc = cursor->get_parent(); + + /* get (or create) the node for this key */ + TransactionNode *node = m_txn_index->get(key, 0); + if (!node) { + node = new TransactionNode(this, key); + node_created = true; + // TODO only store when the operation is successful? + m_txn_index->store(node); + } + + /* check for conflicts of this key - but only if we're not erasing a + * duplicate key. dupes are checked for conflicts in _local_cursor_move TODO that function no longer exists */ + if (!pc || (!pc->get_dupecache_index())) { + st = check_erase_conflicts(context, node, key, flags); + if (st) { + if (node_created) { + m_txn_index->remove(node); + delete node; + } + return (st); + } + } + + /* append a new operation to this node */ + op = node->append(context->txn, flags, TransactionOperation::kErase, + lenv()->next_lsn(), key, 0); + + /* is this function called through ham_cursor_erase? then add the + * duplicate ID */ + if (cursor) { + if (pc->get_dupecache_index()) + op->set_referenced_dupe(pc->get_dupecache_index()); + } + + /* the current op has no cursors attached; but if there are any + * other ops in this node and in this transaction, then they have to + * be set to nil. This only nil's txn-cursors! */ + nil_all_cursors_in_node(context->txn, pc, node); + + /* in addition we nil all btree cursors which are coupled to this key */ + nil_all_cursors_in_btree(context, pc, node->get_key()); + + /* append journal entry */ + if (m_env->get_flags() & HAM_ENABLE_RECOVERY + && m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) { + Journal *j = lenv()->journal(); + j->append_erase(this, context->txn, key, 0, + flags | HAM_ERASE_ALL_DUPLICATES, op->get_lsn()); + } + + ham_assert(st == 0); + return (0); +} + +ham_status_t +LocalDatabase::create(Context *context, PBtreeHeader *btree_header) +{ + /* set the flags; strip off run-time (per session) flags for the btree */ + uint32_t persistent_flags = get_flags(); + persistent_flags &= ~(HAM_CACHE_UNLIMITED + | HAM_DISABLE_MMAP + | HAM_ENABLE_FSYNC + | HAM_READ_ONLY + | HAM_ENABLE_RECOVERY + | HAM_AUTO_RECOVERY + | HAM_ENABLE_TRANSACTIONS); + + switch (m_config.key_type) { + case HAM_TYPE_UINT8: + m_config.key_size = 1; + break; + case HAM_TYPE_UINT16: + m_config.key_size = 2; + break; + case HAM_TYPE_REAL32: + case HAM_TYPE_UINT32: + m_config.key_size = 4; + break; + case HAM_TYPE_REAL64: + case HAM_TYPE_UINT64: + m_config.key_size = 8; + break; + } + + // if we cannot fit at least 10 keys in a page then refuse to continue + if (m_config.key_size != HAM_KEY_SIZE_UNLIMITED) { + if (lenv()->config().page_size_bytes / (m_config.key_size + 8) < 10) { + ham_trace(("key size too large; either increase page_size or decrease " + "key size")); + return (HAM_INV_KEY_SIZE); + } + } + + // fixed length records: + // + // if records are <= 8 bytes OR if we can fit at least 500 keys AND + // records into the leaf then store the records in the leaf; + // otherwise they're allocated as a blob + if (m_config.record_size != HAM_RECORD_SIZE_UNLIMITED) { + if (m_config.record_size <= 8 + || (m_config.record_size <= kInlineRecordThreshold + && lenv()->config().page_size_bytes + / (m_config.key_size + m_config.record_size) > 500)) { + persistent_flags |= HAM_FORCE_RECORDS_INLINE; + m_config.flags |= HAM_FORCE_RECORDS_INLINE; + } + } + + // create the btree + m_btree_index.reset(new BtreeIndex(this, btree_header, persistent_flags, + m_config.key_type, m_config.key_size)); + + /* initialize the btree */ + m_btree_index->create(context, m_config.key_type, m_config.key_size, + m_config.record_size); + + /* the header page is now dirty */ + Page *header = lenv()->page_manager()->fetch(context, 0); + header->set_dirty(true); + + /* and the TransactionIndex */ + m_txn_index.reset(new TransactionIndex(this)); + + return (0); +} + +ham_status_t +LocalDatabase::open(Context *context, PBtreeHeader *btree_header) +{ + /* + * set the database flags; strip off the persistent flags that may have been + * set by the caller, before mixing in the persistent flags as obtained + * from the btree. + */ + uint32_t flags = get_flags(); + flags &= ~(HAM_CACHE_UNLIMITED + | HAM_DISABLE_MMAP + | HAM_ENABLE_FSYNC + | HAM_READ_ONLY + | HAM_ENABLE_RECOVERY + | HAM_AUTO_RECOVERY + | HAM_ENABLE_TRANSACTIONS); + + m_config.key_type = btree_header->get_key_type(); + m_config.key_size = btree_header->get_key_size(); + + /* create the BtreeIndex */ + m_btree_index.reset(new BtreeIndex(this, btree_header, + flags | btree_header->get_flags(), + btree_header->get_key_type(), + btree_header->get_key_size())); + + ham_assert(!(m_btree_index->get_flags() & HAM_CACHE_UNLIMITED)); + ham_assert(!(m_btree_index->get_flags() & HAM_DISABLE_MMAP)); + ham_assert(!(m_btree_index->get_flags() & HAM_ENABLE_FSYNC)); + ham_assert(!(m_btree_index->get_flags() & HAM_READ_ONLY)); + ham_assert(!(m_btree_index->get_flags() & HAM_ENABLE_RECOVERY)); + ham_assert(!(m_btree_index->get_flags() & HAM_AUTO_RECOVERY)); + ham_assert(!(m_btree_index->get_flags() & HAM_ENABLE_TRANSACTIONS)); + + /* initialize the btree */ + m_btree_index->open(); + + /* create the TransactionIndex - TODO only if txn's are enabled? */ + m_txn_index.reset(new TransactionIndex(this)); + + /* merge the non-persistent database flag with the persistent flags from + * the btree index */ + m_config.flags = config().flags | m_btree_index->get_flags(); + m_config.key_size = m_btree_index->get_key_size(); + m_config.key_type = m_btree_index->get_key_type(); + m_config.record_size = m_btree_index->get_record_size(); + + // fetch the current record number + if ((get_flags() & (HAM_RECORD_NUMBER32 | HAM_RECORD_NUMBER64))) { + ham_key_t key = {}; + Cursor *c = new Cursor(this, 0, 0); + ham_status_t st = cursor_move_impl(context, c, &key, 0, HAM_CURSOR_LAST); + cursor_close(c); + if (st) + return (st == HAM_KEY_NOT_FOUND ? 0 : st); + + if (get_flags() & HAM_RECORD_NUMBER32) + m_recno = *(uint32_t *)key.data; + else + m_recno = *(uint64_t *)key.data; + } + + return (0); +} + +struct MetricsVisitor : public BtreeVisitor { + MetricsVisitor(ham_env_metrics_t *metrics) + : m_metrics(metrics) { + } + + // Specifies if the visitor modifies the node + virtual bool is_read_only() const { + return (true); + } + + // called for each node + virtual void operator()(Context *context, BtreeNodeProxy *node) { + if (node->is_leaf()) + node->fill_metrics(&m_metrics->btree_leaf_metrics); + else + node->fill_metrics(&m_metrics->btree_internal_metrics); + } + + ham_env_metrics_t *m_metrics; +}; + +void +LocalDatabase::fill_metrics(ham_env_metrics_t *metrics) +{ + metrics->btree_leaf_metrics.database_name = name(); + metrics->btree_internal_metrics.database_name = name(); + + try { + MetricsVisitor visitor(metrics); + Context context(lenv(), 0, this); + m_btree_index->visit_nodes(&context, visitor, true); + + // calculate the "avg" values + BtreeStatistics::finalize_metrics(&metrics->btree_leaf_metrics); + BtreeStatistics::finalize_metrics(&metrics->btree_internal_metrics); + } + catch (Exception &) { + } +} + +ham_status_t +LocalDatabase::get_parameters(ham_parameter_t *param) +{ + try { + Context context(lenv(), 0, this); + + Page *page = 0; + ham_parameter_t *p = param; + + if (p) { + for (; p->name; p++) { + switch (p->name) { + case HAM_PARAM_KEY_SIZE: + p->value = m_config.key_size; + break; + case HAM_PARAM_KEY_TYPE: + p->value = m_config.key_type; + break; + case HAM_PARAM_RECORD_SIZE: + p->value = m_config.record_size; + break; + case HAM_PARAM_FLAGS: + p->value = (uint64_t)get_flags(); + break; + case HAM_PARAM_DATABASE_NAME: + p->value = (uint64_t)name(); + break; + case HAM_PARAM_MAX_KEYS_PER_PAGE: + p->value = 0; + page = lenv()->page_manager()->fetch(&context, + m_btree_index->get_root_address(), + PageManager::kReadOnly); + if (page) { + BtreeNodeProxy *node = m_btree_index->get_node_from_page(page); + p->value = node->estimate_capacity(); + } + break; + case HAM_PARAM_RECORD_COMPRESSION: + p->value = 0; + break; + case HAM_PARAM_KEY_COMPRESSION: + p->value = 0; + break; + default: + ham_trace(("unknown parameter %d", (int)p->name)); + throw Exception(HAM_INV_PARAMETER); + } + } + } + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +ham_status_t +LocalDatabase::check_integrity(uint32_t flags) +{ + try { + Context context(lenv(), 0, this); + + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(&context); + + /* call the btree function */ + m_btree_index->check_integrity(&context, flags); + + /* call the txn function */ + //m_txn_index->check_integrity(flags); + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +ham_status_t +LocalDatabase::count(Transaction *htxn, bool distinct, uint64_t *pcount) +{ + LocalTransaction *txn = dynamic_cast<LocalTransaction *>(htxn); + + try { + Context context(lenv(), txn, this); + + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(&context); + + /* + * call the btree function - this will retrieve the number of keys + * in the btree + */ + uint64_t keycount = m_btree_index->count(&context, distinct); + + /* + * if transactions are enabled, then also sum up the number of keys + * from the transaction tree + */ + if (get_flags() & HAM_ENABLE_TRANSACTIONS) + keycount += m_txn_index->count(&context, txn, distinct); + + *pcount = keycount; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::scan(Transaction *txn, ScanVisitor *visitor, bool distinct) +{ + ham_status_t st = 0; + + try { + Context context(lenv(), (LocalTransaction *)txn, this); + + Page *page; + ham_key_t key = {0}; + + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(&context); + + /* create a cursor, move it to the first key */ + Cursor *cursor = cursor_create_impl(txn, 0); + + st = cursor_move_impl(&context, cursor, &key, 0, HAM_CURSOR_FIRST); + if (st) + goto bail; + + /* only transaction keys? then use a regular cursor */ + if (!cursor->is_coupled_to_btree()) { + do { + /* process the key */ + (*visitor)(key.data, key.size, distinct + ? cursor->get_record_count(&context, 0) + : 1); + } while ((st = cursor_move_impl(&context, cursor, &key, + 0, HAM_CURSOR_NEXT)) == 0); + goto bail; + } + + /* only btree keys? then traverse page by page */ + if (!(get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_assert(cursor->is_coupled_to_btree()); + + do { + // get the coupled page + cursor->get_btree_cursor()->get_coupled_key(&page); + BtreeNodeProxy *node = m_btree_index->get_node_from_page(page); + // and let the btree node perform the remaining work + node->scan(&context, visitor, 0, distinct); + } while (cursor->get_btree_cursor()->move_to_next_page(&context) == 0); + + goto bail; + } + + /* mixed txn/btree load? if there are btree nodes which are NOT modified + * in transactions then move the scan to the btree node. Otherwise use + * a regular cursor */ + while (true) { + if (!cursor->is_coupled_to_btree()) + break; + + int slot; + cursor->get_btree_cursor()->get_coupled_key(&page, &slot); + BtreeNodeProxy *node = m_btree_index->get_node_from_page(page); + + /* are transactions present? then check if the next txn key is >= btree[0] + * and <= btree[n] */ + ham_key_t *txnkey = 0; + if (cursor->get_txn_cursor()->get_coupled_op()) + txnkey = cursor->get_txn_cursor()->get_coupled_op()->get_node()->get_key(); + // no (more) transactional keys left - process the current key, then + // scan the remaining keys directly in the btree + if (!txnkey) { + /* process the key */ + (*visitor)(key.data, key.size, distinct + ? cursor->get_record_count(&context, 0) + : 1); + break; + } + + /* if yes: use the cursor to traverse the page */ + if (node->compare(&context, txnkey, 0) >= 0 + && node->compare(&context, txnkey, node->get_count() - 1) <= 0) { + do { + Page *new_page = 0; + if (cursor->is_coupled_to_btree()) + cursor->get_btree_cursor()->get_coupled_key(&new_page); + /* break the loop if we've reached the next page */ + if (new_page && new_page != page) { + page = new_page; + break; + } + /* process the key */ + (*visitor)(key.data, key.size, distinct + ? cursor->get_record_count(&context, 0) + : 1); + } while ((st = cursor_move_impl(&context, cursor, &key, + 0, HAM_CURSOR_NEXT)) == 0); + + if (st != HAM_SUCCESS) + goto bail; + } + else { + /* Otherwise traverse directly in the btree page. This is the fastest + * code path. */ + node->scan(&context, visitor, slot, distinct); + /* and then move to the next page */ + if (cursor->get_btree_cursor()->move_to_next_page(&context) != 0) + break; + } + } + + /* pick up the remaining transactional keys */ + while ((st = cursor_move_impl(&context, cursor, &key, + 0, HAM_CURSOR_NEXT)) == 0) { + (*visitor)(key.data, key.size, distinct + ? cursor->get_record_count(&context, 0) + : 1); + } + +bail: + if (cursor) + cursor_close_impl(cursor); + return (st == HAM_KEY_NOT_FOUND ? 0 : st); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::insert(Cursor *cursor, Transaction *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + Context context(lenv(), (LocalTransaction *)txn, this); + + try { + if (m_config.flags & (HAM_RECORD_NUMBER32 | HAM_RECORD_NUMBER64)) { + if (key->size == 0 && key->data == 0) { + // ok! + } + else if (key->size == 0 && key->data != 0) { + ham_trace(("for record number keys set key size to 0, " + "key->data to null")); + return (HAM_INV_PARAMETER); + } + else if (key->size != m_config.key_size) { + ham_trace(("invalid key size (%u instead of %u)", + key->size, m_config.key_size)); + return (HAM_INV_KEY_SIZE); + } + } + else if (m_config.key_size != HAM_KEY_SIZE_UNLIMITED + && key->size != m_config.key_size) { + ham_trace(("invalid key size (%u instead of %u)", + key->size, m_config.key_size)); + return (HAM_INV_KEY_SIZE); + } + if (m_config.record_size != HAM_RECORD_SIZE_UNLIMITED + && record->size != m_config.record_size) { + ham_trace(("invalid record size (%u instead of %u)", + record->size, m_config.record_size)); + return (HAM_INV_RECORD_SIZE); + } + + ByteArray *arena = &key_arena(txn); + + /* + * record number: make sure that we have a valid key structure, + * and lazy load the last used record number + * + * TODO TODO + * too much duplicated code + */ + uint64_t recno = 0; + if (get_flags() & HAM_RECORD_NUMBER64) { + if (flags & HAM_OVERWRITE) { + ham_assert(key->size == sizeof(uint64_t)); + ham_assert(key->data != 0); + recno = *(uint64_t *)key->data; + } + else { + /* get the record number and increment it */ + recno = next_record_number(); + } + + /* allocate memory for the key */ + if (!key->data) { + arena->resize(sizeof(uint64_t)); + key->data = arena->get_ptr(); + } + key->size = sizeof(uint64_t); + *(uint64_t *)key->data = recno; + + /* A recno key is always appended sequentially */ + flags |= HAM_HINT_APPEND; + } + else if (get_flags() & HAM_RECORD_NUMBER32) { + if (flags & HAM_OVERWRITE) { + ham_assert(key->size == sizeof(uint32_t)); + ham_assert(key->data != 0); + recno = *(uint32_t *)key->data; + } + else { + /* get the record number and increment it */ + recno = next_record_number(); + } + + /* allocate memory for the key */ + if (!key->data) { + arena->resize(sizeof(uint32_t)); + key->data = arena->get_ptr(); + } + key->size = sizeof(uint32_t); + *(uint32_t *)key->data = (uint32_t)recno; + + /* A recno key is always appended sequentially */ + flags |= HAM_HINT_APPEND; + } + + ham_status_t st = 0; + LocalTransaction *local_txn = 0; + + /* purge cache if necessary */ + if (!txn && (get_flags() & HAM_ENABLE_TRANSACTIONS)) { + local_txn = begin_temp_txn(); + context.txn = local_txn; + } + + st = insert_impl(&context, cursor, key, record, flags); + return (finalize(&context, st, local_txn)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::erase(Cursor *cursor, Transaction *txn, ham_key_t *key, + uint32_t flags) +{ + Context context(lenv(), (LocalTransaction *)txn, this); + + try { + ham_status_t st = 0; + LocalTransaction *local_txn = 0; + + if (cursor) { + if (cursor->is_nil()) + throw Exception(HAM_CURSOR_IS_NIL); + if (cursor->is_coupled_to_txnop()) // TODO rewrite the next line, it's ugly + key = cursor->get_txn_cursor()->get_coupled_op()->get_node()->get_key(); + else // cursor->is_coupled_to_btree() + key = 0; + } + + if (key) { + if (m_config.key_size != HAM_KEY_SIZE_UNLIMITED + && key->size != m_config.key_size) { + ham_trace(("invalid key size (%u instead of %u)", + key->size, m_config.key_size)); + return (HAM_INV_KEY_SIZE); + } + } + + if (!txn && (get_flags() & HAM_ENABLE_TRANSACTIONS)) { + local_txn = begin_temp_txn(); + context.txn = local_txn; + } + + st = erase_impl(&context, cursor, key, flags); + return (finalize(&context, st, local_txn)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::find(Cursor *cursor, Transaction *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + Context context(lenv(), (LocalTransaction *)txn, this); + + try { + ham_status_t st = 0; + + /* Duplicates AND Transactions require a Cursor because only + * Cursors can build lists of duplicates. + * TODO not exception safe - if find() throws then the cursor is not closed + */ + if (!cursor + && (get_flags() & (HAM_ENABLE_DUPLICATE_KEYS|HAM_ENABLE_TRANSACTIONS))) { + Cursor *c = cursor_create_impl(txn, 0); + st = find(c, txn, key, record, flags); + cursor_close_impl(c); + delete c; + return (st); + } + + if (m_config.key_size != HAM_KEY_SIZE_UNLIMITED + && key->size != m_config.key_size) { + ham_trace(("invalid key size (%u instead of %u)", + key->size, m_config.key_size)); + return (HAM_INV_KEY_SIZE); + } + + // cursor: reset the dupecache, set to nil + // TODO merge both calls, only set to nil if find() was successful + if (cursor) { + cursor->clear_dupecache(); + cursor->set_to_nil(Cursor::kBoth); + } + + st = find_impl(&context, cursor, key, record, flags); + if (st) + return (finalize(&context, st, 0)); + + if (cursor) { + // make sure that txn-cursor and btree-cursor point to the same keys + if (get_flags() & HAM_ENABLE_TRANSACTIONS) { + bool is_equal; + (void)cursor->sync(&context, Cursor::kSyncOnlyEqualKeys, &is_equal); + if (!is_equal && cursor->is_coupled_to_txnop()) + cursor->set_to_nil(Cursor::kBtree); + } + + /* if the key has duplicates: build a duplicate table, then couple to the + * first/oldest duplicate */ + if (get_flags() & HAM_ENABLE_DUPLICATES) + cursor->clear_dupecache(); + + if (cursor->get_dupecache_count(&context)) { + DupeCacheLine *e = cursor->get_dupecache()->get_first_element(); + if (e->use_btree()) + cursor->couple_to_btree(); + else + cursor->couple_to_txnop(); + cursor->couple_to_dupe(1); // 1-based index! + if (record) { // TODO don't copy record if it was already + // copied in find_impl + if (cursor->is_coupled_to_txnop()) + cursor->get_txn_cursor()->copy_coupled_record(record); + else { + Transaction *txn = cursor->get_txn(); + st = cursor->get_btree_cursor()->move(&context, 0, 0, record, + &record_arena(txn), 0); + } + } + } + + /* set a flag that the cursor just completed an Insert-or-find + * operation; this information is needed in ham_cursor_move */ + cursor->set_lastop(Cursor::kLookupOrInsert); + } + + return (finalize(&context, st, 0)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +Cursor * +LocalDatabase::cursor_create_impl(Transaction *txn, uint32_t flags) +{ + return (new Cursor(this, txn, flags)); +} + +Cursor * +LocalDatabase::cursor_clone_impl(Cursor *src) +{ + return (new Cursor(*src)); +} + +ham_status_t +LocalDatabase::cursor_get_record_count(Cursor *cursor, uint32_t flags, + uint32_t *pcount) +{ + try { + Context context(lenv(), (LocalTransaction *)cursor->get_txn(), this); + *pcount = cursor->get_record_count(&context, flags); + return (0); + } + catch (Exception &ex) { + *pcount = 0; + return (ex.code); + } +} + +ham_status_t +LocalDatabase::cursor_get_duplicate_position(Cursor *cursor, + uint32_t *pposition) +{ + try { + *pposition = cursor->get_duplicate_position(); + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::cursor_get_record_size(Cursor *cursor, uint64_t *psize) +{ + try { + Context context(lenv(), (LocalTransaction *)cursor->get_txn(), this); + *psize = cursor->get_record_size(&context); + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::cursor_overwrite(Cursor *cursor, + ham_record_t *record, uint32_t flags) +{ + Context context(lenv(), (LocalTransaction *)cursor->get_txn(), this); + + try { + ham_status_t st = 0; + Transaction *local_txn = 0; + + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(&context); + + /* if user did not specify a transaction, but transactions are enabled: + * create a temporary one */ + if (!cursor->get_txn() && (get_flags() & HAM_ENABLE_TRANSACTIONS)) { + local_txn = begin_temp_txn(); + context.txn = (LocalTransaction *)local_txn; + } + + /* this function will do all the work */ + st = cursor->overwrite(&context, cursor->get_txn() + ? cursor->get_txn() + : local_txn, + record, flags); + return (finalize(&context, st, local_txn)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::cursor_move(Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + try { + Context context(lenv(), (LocalTransaction *)cursor->get_txn(), + this); + + return (cursor_move_impl(&context, cursor, key, record, flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +LocalDatabase::cursor_move_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags) +{ + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(context); + + /* + * if the cursor was never used before and the user requests a NEXT then + * move the cursor to FIRST; if the user requests a PREVIOUS we set it + * to LAST, resp. + * + * if the cursor was already used but is nil then we've reached EOF, + * and a NEXT actually tries to move to the LAST key (and PREVIOUS + * moves to FIRST) + * + * TODO the btree-cursor has identical code which can be removed + */ + if (cursor->is_nil(0)) { + if (flags & HAM_CURSOR_NEXT) { + flags &= ~HAM_CURSOR_NEXT; + if (cursor->is_first_use()) + flags |= HAM_CURSOR_FIRST; + else + flags |= HAM_CURSOR_LAST; + } + else if (flags & HAM_CURSOR_PREVIOUS) { + flags &= ~HAM_CURSOR_PREVIOUS; + if (cursor->is_first_use()) + flags |= HAM_CURSOR_LAST; + else + flags |= HAM_CURSOR_FIRST; + } + } + + ham_status_t st = 0; + + /* in non-transactional mode - just call the btree function and return */ + if (!(get_flags() & HAM_ENABLE_TRANSACTIONS)) { + return (cursor->get_btree_cursor()->move(context, + key, &key_arena(context->txn), + record, &record_arena(context->txn), flags)); + } + + /* everything else is handled by the cursor function */ + st = cursor->move(context, key, record, flags); + + /* store the direction */ + if (flags & HAM_CURSOR_NEXT) + cursor->set_lastop(HAM_CURSOR_NEXT); + else if (flags & HAM_CURSOR_PREVIOUS) + cursor->set_lastop(HAM_CURSOR_PREVIOUS); + else + cursor->set_lastop(0); + + if (st) { + if (st == HAM_KEY_ERASED_IN_TXN) + st = HAM_KEY_NOT_FOUND; + /* trigger a sync when the function is called again */ + cursor->set_lastop(0); + return (st); + } + + return (0); +} + +void +LocalDatabase::cursor_close_impl(Cursor *cursor) +{ + cursor->close(); +} + +ham_status_t +LocalDatabase::close_impl(uint32_t flags) +{ + Context context(lenv(), 0, this); + + /* check if this database is modified by an active transaction */ + if (m_txn_index) { + TransactionNode *node = m_txn_index->get_first(); + while (node) { + TransactionOperation *op = node->get_newest_op(); + while (op) { + Transaction *optxn = op->get_txn(); + if (!optxn->is_committed() && !optxn->is_aborted()) { + ham_trace(("cannot close a Database that is modified by " + "a currently active Transaction")); + return (set_error(HAM_TXN_STILL_OPEN)); + } + op = op->get_previous_in_node(); + } + node = node->get_next_sibling(); + } + } + + /* in-memory-database: free all allocated blobs */ + if (m_btree_index && m_env->get_flags() & HAM_IN_MEMORY) + m_btree_index->release(&context); + + /* + * flush all pages of this database (but not the header page, + * it's still required and will be flushed below) + */ + lenv()->page_manager()->close_database(&context, this); + + return (0); +} + +void +LocalDatabase::increment_dupe_index(Context *context, TransactionNode *node, + Cursor *skip, uint32_t start) +{ + Cursor *c = m_cursor_list; + + while (c) { + bool hit = false; + + if (c == skip || c->is_nil(0)) + goto next; + + /* if cursor is coupled to an op in the same node: increment + * duplicate index (if required) */ + if (c->is_coupled_to_txnop()) { + TransactionCursor *txnc = c->get_txn_cursor(); + TransactionNode *n = txnc->get_coupled_op()->get_node(); + if (n == node) + hit = true; + } + /* if cursor is coupled to the same key in the btree: increment + * duplicate index (if required) */ + else if (c->get_btree_cursor()->points_to(context, node->get_key())) { + hit = true; + } + + if (hit) { + if (c->get_dupecache_index() > start) + c->set_dupecache_index(c->get_dupecache_index() + 1); + } + +next: + c = c->get_next(); + } +} + +void +LocalDatabase::nil_all_cursors_in_node(LocalTransaction *txn, Cursor *current, + TransactionNode *node) +{ + TransactionOperation *op = node->get_newest_op(); + while (op) { + TransactionCursor *cursor = op->cursor_list(); + while (cursor) { + Cursor *parent = cursor->get_parent(); + // is the current cursor to a duplicate? then adjust the + // coupled duplicate index of all cursors which point to a duplicate + if (current) { + if (current->get_dupecache_index()) { + if (current->get_dupecache_index() < parent->get_dupecache_index()) { + parent->set_dupecache_index(parent->get_dupecache_index() - 1); + cursor = cursor->get_coupled_next(); + continue; + } + else if (current->get_dupecache_index() > parent->get_dupecache_index()) { + cursor = cursor->get_coupled_next(); + continue; + } + // else fall through + } + } + parent->couple_to_btree(); // TODO merge these two lines + parent->set_to_nil(Cursor::kTxn); + // set a flag that the cursor just completed an Insert-or-find + // operation; this information is needed in ham_cursor_move + // (in this aspect, an erase is the same as insert/find) + parent->set_lastop(Cursor::kLookupOrInsert); + + cursor = op->cursor_list(); + } + + op = op->get_previous_in_node(); + } +} + +ham_status_t +LocalDatabase::copy_record(LocalDatabase *db, Transaction *txn, + TransactionOperation *op, ham_record_t *record) +{ + ByteArray *arena = &db->record_arena(txn); + + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + arena->resize(op->get_record()->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, op->get_record()->data, op->get_record()->size); + record->size = op->get_record()->size; + return (0); +} + +void +LocalDatabase::nil_all_cursors_in_btree(Context *context, Cursor *current, + ham_key_t *key) +{ + Cursor *c = m_cursor_list; + + /* foreach cursor in this database: + * if it's nil or coupled to the txn: skip it + * if it's coupled to btree AND uncoupled: compare keys; set to nil + * if keys are identical + * if it's uncoupled to btree AND coupled: compare keys; set to nil + * if keys are identical; (TODO - improve performance by nil'ling + * all other cursors from the same btree page) + * + * do NOT nil the current cursor - it's coupled to the key, and the + * coupled key is still needed by the caller + */ + while (c) { + if (c->is_nil(0) || c == current) + goto next; + if (c->is_coupled_to_txnop()) + goto next; + + if (c->get_btree_cursor()->points_to(context, key)) { + /* is the current cursor to a duplicate? then adjust the + * coupled duplicate index of all cursors which point to a + * duplicate */ + if (current) { + if (current->get_dupecache_index()) { + if (current->get_dupecache_index() < c->get_dupecache_index()) { + c->set_dupecache_index(c->get_dupecache_index() - 1); + goto next; + } + else if (current->get_dupecache_index() > c->get_dupecache_index()) { + goto next; + } + /* else fall through */ + } + } + c->set_to_nil(0); + } +next: + c = c->get_next(); + } +} + +ham_status_t +LocalDatabase::flush_txn_operation(Context *context, LocalTransaction *txn, + TransactionOperation *op) +{ + ham_status_t st = 0; + TransactionNode *node = op->get_node(); + + /* + * depending on the type of the operation: actually perform the + * operation on the btree + * + * if the txn-op has a cursor attached, then all (txn)cursors + * which are coupled to this op have to be uncoupled, and their + * parent (btree) cursor must be coupled to the btree item instead. + */ + if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite) + || (op->get_flags() & TransactionOperation::kInsertDuplicate)) { + uint32_t additional_flag = + (op->get_flags() & TransactionOperation::kInsertDuplicate) + ? HAM_DUPLICATE + : HAM_OVERWRITE; + if (!op->cursor_list()) { + st = m_btree_index->insert(context, 0, node->get_key(), op->get_record(), + op->get_orig_flags() | additional_flag); + } + else { + TransactionCursor *tc1 = op->cursor_list(); + Cursor *c1 = tc1->get_parent(); + /* pick the first cursor, get the parent/btree cursor and + * insert the key/record pair in the btree. The btree cursor + * then will be coupled to this item. */ + st = m_btree_index->insert(context, c1, node->get_key(), op->get_record(), + op->get_orig_flags() | additional_flag); + if (!st) { + /* uncouple the cursor from the txn-op, and remove it */ + c1->couple_to_btree(); // TODO merge these two calls + c1->set_to_nil(Cursor::kTxn); + + /* all other (btree) cursors need to be coupled to the same + * item as the first one. */ + TransactionCursor *tc2; + while ((tc2 = op->cursor_list())) { + Cursor *c2 = tc2->get_parent(); + c2->get_btree_cursor()->clone(c1->get_btree_cursor()); + c2->couple_to_btree(); // TODO merge these two calls + c2->set_to_nil(Cursor::kTxn); + } + } + } + } + else if (op->get_flags() & TransactionOperation::kErase) { + st = m_btree_index->erase(context, 0, node->get_key(), + op->get_referenced_dupe(), op->get_flags()); + if (st == HAM_KEY_NOT_FOUND) + st = 0; + } + + return (st); +} + +ham_status_t +LocalDatabase::drop(Context *context) +{ + m_btree_index->release(context); + return (0); +} + +ham_status_t +LocalDatabase::insert_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags) +{ + ham_status_t st = 0; + + lenv()->page_manager()->purge_cache(context); + + /* + * if transactions are enabled: only insert the key/record pair into + * the Transaction structure. Otherwise immediately write to the btree. + */ + if (context->txn || m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) + st = insert_txn(context, key, record, flags, cursor + ? cursor->get_txn_cursor() + : 0); + else + st = m_btree_index->insert(context, cursor, key, record, flags); + + // couple the cursor to the inserted key + if (st == 0 && cursor) { + if (m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) { + DupeCache *dc = cursor->get_dupecache(); + // TODO required? should have happened in insert_txn + cursor->couple_to_txnop(); + /* the cursor is coupled to the txn-op; nil the btree-cursor to + * trigger a sync() call when fetching the duplicates */ + // TODO merge with the line above + cursor->set_to_nil(Cursor::kBtree); + + /* reset the dupecache, otherwise cursor->get_dupecache_count() + * does not update the dupecache correctly */ + dc->clear(); + + /* if duplicate keys are enabled: set the duplicate index of + * the new key */ + if (st == 0 && cursor->get_dupecache_count(context)) { + TransactionOperation *op = cursor->get_txn_cursor()->get_coupled_op(); + ham_assert(op != 0); + + for (uint32_t i = 0; i < dc->get_count(); i++) { + DupeCacheLine *l = dc->get_element(i); + if (!l->use_btree() && l->get_txn_op() == op) { + cursor->set_dupecache_index(i + 1); + break; + } + } + } + } + else { + // TODO required? should have happened in BtreeInsertAction + cursor->couple_to_btree(); + } + + /* set a flag that the cursor just completed an Insert-or-find + * operation; this information is needed in ham_cursor_move */ + cursor->set_lastop(Cursor::kLookupOrInsert); + } + + return (st); +} + +ham_status_t +LocalDatabase::find_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags) +{ + /* purge cache if necessary */ + lenv()->page_manager()->purge_cache(context); + + /* + * if transactions are enabled: read keys from transaction trees, + * otherwise read immediately from disk + */ + if (context->txn || m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) + return (find_txn(context, cursor, key, record, flags)); + + return (m_btree_index->find(context, cursor, key, &key_arena(context->txn), + record, &record_arena(context->txn), flags)); +} + +ham_status_t +LocalDatabase::erase_impl(Context *context, Cursor *cursor, ham_key_t *key, + uint32_t flags) +{ + ham_status_t st = 0; + + /* + * if transactions are enabled: append a 'erase key' operation into + * the txn tree; otherwise immediately erase the key from disk + */ + if (context->txn || m_env->get_flags() & HAM_ENABLE_TRANSACTIONS) { + if (cursor) { + /* + * !! + * we have two cases: + * + * 1. the cursor is coupled to a btree item (or uncoupled, but not nil) + * and the txn_cursor is nil; in that case, we have to + * - uncouple the btree cursor + * - insert the erase-op for the key which is used by the btree cursor + * + * 2. the cursor is coupled to a txn-op; in this case, we have to + * - insert the erase-op for the key which is used by the txn-op + * + * TODO clean up this whole mess. code should be like + * + * if (txn) + * erase_txn(txn, cursor->get_key(), 0, cursor->get_txn_cursor()); + */ + /* case 1 described above */ + if (cursor->is_coupled_to_btree()) { + cursor->set_to_nil(Cursor::kTxn); + cursor->get_btree_cursor()->uncouple_from_page(context); + st = erase_txn(context, cursor->get_btree_cursor()->get_uncoupled_key(), + 0, cursor->get_txn_cursor()); + } + /* case 2 described above */ + else { + // TODO this line is ugly + st = erase_txn(context, + cursor->get_txn_cursor()->get_coupled_op()->get_key(), + 0, cursor->get_txn_cursor()); + } + } + else { + st = erase_txn(context, key, flags, 0); + } + } + else { + st = m_btree_index->erase(context, cursor, key, 0, flags); + } + + /* on success: verify that cursor is now nil */ + if (cursor && st == 0) { + cursor->set_to_nil(0); + cursor->couple_to_btree(); // TODO why? + ham_assert(cursor->get_txn_cursor()->is_nil()); + ham_assert(cursor->is_nil(0)); + cursor->clear_dupecache(); // TODO merge with set_to_nil() + } + + return (st); +} + +ham_status_t +LocalDatabase::finalize(Context *context, ham_status_t status, + Transaction *local_txn) +{ + LocalEnvironment *env = lenv(); + + if (status) { + if (local_txn) { + context->changeset.clear(); + env->txn_manager()->abort(local_txn); + } + return (status); + } + + if (local_txn) { + context->changeset.clear(); + env->txn_manager()->commit(local_txn); + } + else if (env->get_flags() & HAM_ENABLE_RECOVERY + && !(env->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + context->changeset.flush(env->next_lsn()); + } + return (0); +} + +LocalTransaction * +LocalDatabase::begin_temp_txn() +{ + LocalTransaction *txn; + ham_status_t st = lenv()->txn_begin((Transaction **)&txn, 0, + HAM_TXN_TEMPORARY | HAM_DONT_LOCK); + if (st) + throw Exception(st); + return (txn); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.h b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.h new file mode 100644 index 0000000000..0d08bd79ed --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_local.h @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: no + */ + +#ifndef HAM_DB_LOCAL_H +#define HAM_DB_LOCAL_H + +#include "0root/root.h" + +#include <limits> + +// Always verify that a file of level N does not include headers > N! +#include "1base/scoped_ptr.h" +#include "3btree/btree_index.h" +#include "4txn/txn_local.h" +#include "4db/db.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class TransactionNode; +class TransactionIndex; +class TransactionCursor; +class TransactionOperation; +class LocalEnvironment; +class LocalTransaction; + +template<typename T> +class RecordNumberFixture; + +// +// The database implementation for local file access +// +class LocalDatabase : public Database { + public: + enum { + // The default threshold for inline records + kInlineRecordThreshold = 32 + }; + + // Constructor + LocalDatabase(Environment *env, DatabaseConfiguration &config) + : Database(env, config), m_recno(0), m_cmp_func(0) { + } + + // Returns the btree index + BtreeIndex *btree_index() { + return (m_btree_index.get()); + } + + // Returns the transactional index + TransactionIndex *txn_index() { + return (m_txn_index.get()); + } + + // Returns the LocalEnvironment instance + LocalEnvironment *lenv() { + return ((LocalEnvironment *)m_env); + } + + // Creates a new Database + ham_status_t create(Context *context, PBtreeHeader *btree_header); + + // Opens an existing Database + ham_status_t open(Context *context, PBtreeHeader *btree_header); + + // Erases this Database + ham_status_t drop(Context *context); + + // Fills in the current metrics + virtual void fill_metrics(ham_env_metrics_t *metrics); + + // Returns Database parameters (ham_db_get_parameters) + virtual ham_status_t get_parameters(ham_parameter_t *param); + + // Checks Database integrity (ham_db_check_integrity) + virtual ham_status_t check_integrity(uint32_t flags); + + // Returns the number of keys + virtual ham_status_t count(Transaction *txn, bool distinct, + uint64_t *pcount); + + // Scans the whole database, applies a processor function + virtual ham_status_t scan(Transaction *txn, ScanVisitor *visitor, + bool distinct); + + // Inserts a key/value pair (ham_db_insert, ham_cursor_insert) + virtual ham_status_t insert(Cursor *cursor, Transaction *txn, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // Erase a key/value pair (ham_db_erase, ham_cursor_erase) + virtual ham_status_t erase(Cursor *cursor, Transaction *txn, ham_key_t *key, + uint32_t flags); + + // Lookup of a key/value pair (ham_db_find, ham_cursor_find) + virtual ham_status_t find(Cursor *cursor, Transaction *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags); + + // Returns number of duplicates (ham_cursor_get_record_count) + virtual ham_status_t cursor_get_record_count(Cursor *cursor, uint32_t flags, + uint32_t *pcount); + + // Returns position in duplicate list (ham_cursor_get_duplicate_position) + virtual ham_status_t cursor_get_duplicate_position(Cursor *cursor, + uint32_t *pposition); + + // Get current record size (ham_cursor_get_record_size) + virtual ham_status_t cursor_get_record_size(Cursor *cursor, + uint64_t *psize); + + // Overwrites the record of a cursor (ham_cursor_overwrite) + virtual ham_status_t cursor_overwrite(Cursor *cursor, + ham_record_t *record, uint32_t flags); + + // Moves a cursor, returns key and/or record (ham_cursor_move) + virtual ham_status_t cursor_move(Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + + // Inserts a key/record pair in a txn node; if cursor is not NULL it will + // be attached to the new txn_op structure + // TODO this should be private + ham_status_t insert_txn(Context *context, ham_key_t *key, + ham_record_t *record, uint32_t flags, + TransactionCursor *cursor); + + // Returns the default comparison function + ham_compare_func_t compare_func() { + return (m_cmp_func); + } + + // Sets the default comparison function (ham_db_set_compare_func) + ham_status_t set_compare_func(ham_compare_func_t f) { + if (m_config.key_type != HAM_TYPE_CUSTOM) { + ham_trace(("ham_set_compare_func only allowed for HAM_TYPE_CUSTOM " + "databases!")); + return (HAM_INV_PARAMETER); + } + m_cmp_func = f; + return (0); + } + + // Flushes a TransactionOperation to the btree + // TODO should be private + ham_status_t flush_txn_operation(Context *context, LocalTransaction *txn, + TransactionOperation *op); + + protected: + friend class Cursor; + + // Copies the ham_record_t structure from |op| into |record| + static ham_status_t copy_record(LocalDatabase *db, Transaction *txn, + TransactionOperation *op, ham_record_t *record); + + // Creates a cursor; this is the actual implementation + virtual Cursor *cursor_create_impl(Transaction *txn, uint32_t flags); + + // Clones a cursor; this is the actual implementation + virtual Cursor *cursor_clone_impl(Cursor *src); + + // Closes a cursor; this is the actual implementation + virtual void cursor_close_impl(Cursor *c); + + // Closes a database; this is the actual implementation + virtual ham_status_t close_impl(uint32_t flags); + + private: + friend struct DbFixture; + friend struct HamsterdbFixture; + friend struct ExtendedKeyFixture; + friend class RecordNumberFixture<uint32_t>; + friend class RecordNumberFixture<uint64_t>; + + // Erases a key/record pair from a txn; on success, cursor will be set to + // nil + ham_status_t erase_txn(Context *context, ham_key_t *key, uint32_t flags, + TransactionCursor *cursor); + + // Lookup of a key/record pair in the Transaction index and in the btree, + // if transactions are disabled/not successful; copies the + // record into |record|. Also performs approx. matching. + ham_status_t find_txn(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // Moves a cursor, returns key and/or record (ham_cursor_move) + ham_status_t cursor_move_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // The actual implementation of insert() + ham_status_t insert_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // The actual implementation of find() + ham_status_t find_impl(Context *context, Cursor *cursor, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // The actual implementation of erase() + ham_status_t erase_impl(Context *context, Cursor *cursor, + ham_key_t *key, uint32_t flags); + + // Finalizes an operation by committing or aborting the |local_txn| + // and clearing or flushing the Changeset. + // Returns |status|. + ham_status_t finalize(Context *context, ham_status_t status, + Transaction *local_txn); + + // Begins a new temporary Transaction + LocalTransaction *begin_temp_txn(); + + // returns the next record number + uint64_t next_record_number() { + m_recno++; + if (m_config.flags & HAM_RECORD_NUMBER32 + && m_recno > std::numeric_limits<uint32_t>::max()) + throw Exception(HAM_LIMITS_REACHED); + else if (m_recno == 0) + throw Exception(HAM_LIMITS_REACHED); + return (m_recno); + } + + // Checks if an insert operation conflicts with another txn; this is the + // case if the same key is modified by another active txn. + ham_status_t check_insert_conflicts(Context *context, TransactionNode *node, + ham_key_t *key, uint32_t flags); + + // Checks if an erase operation conflicts with another txn; this is the + // case if the same key is modified by another active txn. + ham_status_t check_erase_conflicts(Context *context, TransactionNode *node, + ham_key_t *key, uint32_t flags); + + // Increments dupe index of all cursors with a dupe index > |start|; + // only cursor |skip| is ignored + void increment_dupe_index(Context *context, TransactionNode *node, + Cursor *skip, uint32_t start); + + // Sets all cursors attached to a TransactionNode to nil + void nil_all_cursors_in_node(LocalTransaction *txn, Cursor *current, + TransactionNode *node); + + // Sets all cursors to nil if they point to |key| in the btree index + void nil_all_cursors_in_btree(Context *context, Cursor *current, + ham_key_t *key); + + // the current record number + uint64_t m_recno; + + // the btree index + ScopedPtr<BtreeIndex> m_btree_index; + + // the transaction index + ScopedPtr<TransactionIndex> m_txn_index; + + // the comparison function + ham_compare_func_t m_cmp_func; +}; + +} // namespace hamsterdb + +#endif /* HAM_DB_LOCAL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.cc b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.cc new file mode 100644 index 0000000000..58bd49f4db --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.cc @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "1base/scoped_ptr.h" +#include "2protobuf/protocol.h" +#include "4db/db_remote.h" +#include "4env/env_remote.h" +#include "4txn/txn_remote.h" +#include "4cursor/cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +ham_status_t +RemoteDatabase::get_parameters(ham_parameter_t *param) +{ + try { + RemoteEnvironment *env = renv(); + + Protocol request(Protocol::DB_GET_PARAMETERS_REQUEST); + request.mutable_db_get_parameters_request()->set_db_handle(m_remote_handle); + + ham_parameter_t *p = param; + if (p) { + for (; p->name; p++) + request.mutable_db_get_parameters_request()->add_names(p->name); + } + + ScopedPtr<Protocol> reply(env->perform_request(&request)); + + ham_assert(reply->has_db_get_parameters_reply()); + + ham_status_t st = reply->db_get_parameters_reply().status(); + if (st) + throw Exception(st); + + p = param; + while (p && p->name) { + switch (p->name) { + case HAM_PARAM_FLAGS: + ham_assert(reply->db_get_parameters_reply().has_flags()); + p->value = reply->db_get_parameters_reply().flags(); + break; + case HAM_PARAM_KEY_SIZE: + ham_assert(reply->db_get_parameters_reply().has_key_size()); + p->value = reply->db_get_parameters_reply().key_size(); + break; + case HAM_PARAM_RECORD_SIZE: + ham_assert(reply->db_get_parameters_reply().has_record_size()); + p->value = reply->db_get_parameters_reply().record_size(); + break; + case HAM_PARAM_KEY_TYPE: + ham_assert(reply->db_get_parameters_reply().has_key_type()); + p->value = reply->db_get_parameters_reply().key_type(); + break; + case HAM_PARAM_DATABASE_NAME: + ham_assert(reply->db_get_parameters_reply().has_dbname()); + p->value = reply->db_get_parameters_reply().dbname(); + break; + case HAM_PARAM_MAX_KEYS_PER_PAGE: + ham_assert(reply->db_get_parameters_reply().has_keys_per_page()); + p->value = reply->db_get_parameters_reply().keys_per_page(); + break; + default: + ham_trace(("unknown parameter %d", (int)p->name)); + break; + } + p++; + } + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::check_integrity(uint32_t flags) +{ + try { + RemoteEnvironment *env = renv(); + + Protocol request(Protocol::DB_CHECK_INTEGRITY_REQUEST); + request.mutable_db_check_integrity_request()->set_db_handle(m_remote_handle); + request.mutable_db_check_integrity_request()->set_flags(flags); + + std::auto_ptr<Protocol> reply(env->perform_request(&request)); + + ham_assert(reply->has_db_check_integrity_reply()); + + return (reply->db_check_integrity_reply().status()); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::count(Transaction *htxn, bool distinct, uint64_t *pcount) +{ + try { + RemoteEnvironment *env = renv(); + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(htxn); + + SerializedWrapper request; + request.id = kDbGetKeyCountRequest; + request.db_count_request.db_handle = m_remote_handle; + request.db_count_request.txn_handle = txn + ? txn->get_remote_handle() + : 0; + request.db_count_request.distinct = distinct; + + SerializedWrapper reply; + env->perform_request(&request, &reply); + + ham_assert(reply.id == kDbGetKeyCountReply); + + ham_status_t st = reply.db_count_reply.status; + if (st) + return (st); + + *pcount = reply.db_count_reply.keycount; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::insert(Cursor *cursor, Transaction *htxn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + try { + bool send_key = true; + RemoteEnvironment *env = renv(); + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(htxn); + + ByteArray *arena = &key_arena(txn); + + /* recno: do not send the key */ + if (get_flags() & HAM_RECORD_NUMBER32) { + send_key = false; + if (!key->data) { + arena->resize(sizeof(uint32_t)); + key->data = arena->get_ptr(); + key->size = sizeof(uint32_t); + } + } + else if (get_flags() & HAM_RECORD_NUMBER64) { + send_key = false; + if (!key->data) { + arena->resize(sizeof(uint64_t)); + key->data = arena->get_ptr(); + key->size = sizeof(uint64_t); + } + } + + SerializedWrapper request; + SerializedWrapper reply; + + if (cursor) { + SerializedWrapper request; + request.id = kCursorInsertRequest; + request.cursor_insert_request.cursor_handle = cursor->get_remote_handle(); + request.cursor_insert_request.flags = flags; + if (send_key) { + request.cursor_insert_request.has_key = true; + request.cursor_insert_request.key.has_data = true; + request.cursor_insert_request.key.data.size = key->size; + request.cursor_insert_request.key.data.value = (uint8_t *)key->data; + request.cursor_insert_request.key.flags = key->flags; + request.cursor_insert_request.key.intflags = key->_flags; + } + if (record) { + request.cursor_insert_request.has_record = true; + request.cursor_insert_request.record.has_data = true; + request.cursor_insert_request.record.data.size = record->size; + request.cursor_insert_request.record.data.value = (uint8_t *)record->data; + request.cursor_insert_request.record.flags = record->flags; + request.cursor_insert_request.record.partial_size = record->partial_size; + request.cursor_insert_request.record.partial_offset = record->partial_offset; + } + + env->perform_request(&request, &reply); + + ham_assert(reply.id == kCursorInsertReply); + + ham_status_t st = reply.cursor_insert_reply.status; + if (st) + return (st); + + if (reply.cursor_insert_reply.has_key) { + ham_assert(key->size == reply.cursor_insert_reply.key.data.size); + ham_assert(key->data != 0); + ::memcpy(key->data, reply.cursor_insert_reply.key.data.value, key->size); + } + } + else { + request.id = kDbInsertRequest; + request.db_insert_request.db_handle = m_remote_handle; + request.db_insert_request.txn_handle = txn ? txn->get_remote_handle() : 0; + request.db_insert_request.flags = flags; + if (key && !(get_flags() & (HAM_RECORD_NUMBER32 | HAM_RECORD_NUMBER64))) { + request.db_insert_request.has_key = true; + request.db_insert_request.key.has_data = true; + request.db_insert_request.key.data.size = key->size; + request.db_insert_request.key.data.value = (uint8_t *)key->data; + request.db_insert_request.key.flags = key->flags; + request.db_insert_request.key.intflags = key->_flags; + } + if (record) { + request.db_insert_request.has_record = true; + request.db_insert_request.record.has_data = true; + request.db_insert_request.record.data.size = record->size; + request.db_insert_request.record.data.value = (uint8_t *)record->data; + request.db_insert_request.record.flags = record->flags; + request.db_insert_request.record.partial_size = record->partial_size; + request.db_insert_request.record.partial_offset = record->partial_offset; + } + + env->perform_request(&request, &reply); + + ham_assert(reply.id == kDbInsertReply); + + ham_status_t st = reply.db_insert_reply.status; + if (st) + return (st); + + if (reply.db_insert_reply.has_key) { + ham_assert(key->data != 0); + ham_assert(key->size == reply.db_insert_reply.key.data.size); + ::memcpy(key->data, reply.db_insert_reply.key.data.value, key->size); + } + } + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::erase(Cursor *cursor, Transaction *htxn, ham_key_t *key, + uint32_t flags) +{ + try { + if (cursor) { + SerializedWrapper request; + request.id = kCursorEraseRequest; + request.cursor_erase_request.cursor_handle = cursor->get_remote_handle(); + request.cursor_erase_request.flags = flags; + + SerializedWrapper reply; + renv()->perform_request(&request, &reply); + ham_assert(reply.id == kCursorEraseReply); + return (reply.cursor_erase_reply.status); + } + + RemoteEnvironment *env = renv(); + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(htxn); + + SerializedWrapper request; + request.id = kDbEraseRequest; + request.db_erase_request.db_handle = m_remote_handle; + request.db_erase_request.txn_handle = txn ? txn->get_remote_handle() : 0; + request.db_erase_request.flags = flags; + request.db_erase_request.key.has_data = true; + request.db_erase_request.key.data.size = key->size; + request.db_erase_request.key.data.value = (uint8_t *)key->data; + request.db_erase_request.key.flags = key->flags; + request.db_erase_request.key.intflags = key->_flags; + + SerializedWrapper reply; + env->perform_request(&request, &reply); + + ham_assert(reply.id == kDbEraseReply); + + return (reply.db_erase_reply.status); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::find(Cursor *cursor, Transaction *htxn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + try { + if (cursor && !htxn) + htxn = cursor->get_txn(); + + RemoteEnvironment *env = renv(); + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(htxn); + + SerializedWrapper request; + request.id = kDbFindRequest; + request.db_find_request.db_handle = m_remote_handle; + request.db_find_request.cursor_handle = cursor ? cursor->get_remote_handle() : 0; + request.db_find_request.txn_handle = txn ? txn->get_remote_handle() : 0; + request.db_find_request.flags = flags; + request.db_find_request.key.has_data = true; + request.db_find_request.key.data.size = key->size; + request.db_find_request.key.data.value = (uint8_t *)key->data; + request.db_find_request.key.flags = key->flags; + request.db_find_request.key.intflags = key->_flags; + if (record) { + request.db_find_request.has_record = true; + request.db_find_request.record.has_data = true; + request.db_find_request.record.data.size = record->size; + request.db_find_request.record.data.value = (uint8_t *)record->data; + request.db_find_request.record.flags = record->flags; + request.db_find_request.record.partial_size = record->partial_size; + request.db_find_request.record.partial_offset = record->partial_offset; + } + + SerializedWrapper reply; + env->perform_request(&request, &reply); + ham_assert(reply.id == kDbFindReply); + + ByteArray *pkey_arena = &key_arena(txn); + ByteArray *rec_arena = &record_arena(txn); + + ham_status_t st = reply.db_find_reply.status; + if (st == 0) { + /* approx. matching: need to copy the _flags and the key data! */ + if (reply.db_find_reply.has_key) { + ham_assert(key); + key->_flags = reply.db_find_reply.key.intflags; + key->size = (uint16_t)reply.db_find_reply.key.data.size; + if (!(key->flags & HAM_KEY_USER_ALLOC)) { + pkey_arena->resize(key->size); + key->data = pkey_arena->get_ptr(); + } + ::memcpy(key->data, (void *)reply.db_find_reply.key.data.value, + key->size); + } + if (record && reply.db_find_reply.has_record) { + record->size = reply.db_find_reply.record.data.size; + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + rec_arena->resize(record->size); + record->data = rec_arena->get_ptr(); + } + ::memcpy(record->data, (void *)reply.db_find_reply.record.data.value, + record->size); + } + } + return (st); + } + catch (Exception &ex) { + return (ex.code); + } +} + +Cursor * +RemoteDatabase::cursor_create_impl(Transaction *htxn, uint32_t flags) +{ + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(htxn); + + SerializedWrapper request; + request.id = kCursorCreateRequest; + request.cursor_create_request.db_handle = m_remote_handle; + request.cursor_create_request.txn_handle = txn + ? txn->get_remote_handle() + : 0; + request.cursor_create_request.flags = flags; + + SerializedWrapper reply; + renv()->perform_request(&request, &reply); + ham_assert(reply.id == kCursorCreateReply); + ham_status_t st = reply.cursor_create_reply.status; + if (st) + return (0); + + Cursor *c = new Cursor((LocalDatabase *)this); // TODO this cast is evil!! + c->set_remote_handle(reply.cursor_create_reply.cursor_handle); + return (c); +} + +Cursor * +RemoteDatabase::cursor_clone_impl(Cursor *src) +{ + SerializedWrapper request; + request.id = kCursorCloneRequest; + request.cursor_clone_request.cursor_handle = src->get_remote_handle(); + + SerializedWrapper reply; + renv()->perform_request(&request, &reply); + ham_assert(reply.id == kCursorCloneReply); + ham_status_t st = reply.cursor_clone_reply.status; + if (st) + return (0); + + Cursor *c = new Cursor(src->get_db()); + c->set_remote_handle(reply.cursor_clone_reply.cursor_handle); + return (c); +} + +ham_status_t +RemoteDatabase::cursor_get_record_count(Cursor *cursor, uint32_t flags, + uint32_t *pcount) +{ + try { + RemoteEnvironment *env = renv(); + + SerializedWrapper request; + request.id = kCursorGetRecordCountRequest; + request.cursor_get_record_count_request.cursor_handle = + cursor->get_remote_handle(); + request.cursor_get_record_count_request.flags = flags; + + SerializedWrapper reply; + env->perform_request(&request, &reply); + ham_assert(reply.id == kCursorGetRecordCountReply); + + ham_status_t st = reply.cursor_get_record_count_reply.status; + if (st == 0) + *pcount = reply.cursor_get_record_count_reply.count; + else + *pcount = 0; + return (st); + } + catch (Exception &ex) { + *pcount = 0; + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::cursor_get_duplicate_position(Cursor *cursor, + uint32_t *pposition) +{ + try { + RemoteEnvironment *env = renv(); + + SerializedWrapper request; + request.id = kCursorGetDuplicatePositionRequest; + request.cursor_get_duplicate_position_request.cursor_handle = + cursor->get_remote_handle(); + + SerializedWrapper reply; + env->perform_request(&request, &reply); + ham_assert(reply.id == kCursorGetDuplicatePositionReply); + + ham_status_t st = reply.cursor_get_duplicate_position_reply.status; + if (st == 0) + *pposition = reply.cursor_get_duplicate_position_reply.position; + return (st); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::cursor_get_record_size(Cursor *cursor, uint64_t *psize) +{ + try { + RemoteEnvironment *env = renv(); + + SerializedWrapper request; + request.id = kCursorGetRecordSizeRequest; + request.cursor_get_record_size_request.cursor_handle = + cursor->get_remote_handle(); + + SerializedWrapper reply; + env->perform_request(&request, &reply); + ham_assert(reply.id == kCursorGetRecordSizeReply); + + ham_status_t st = reply.cursor_get_record_size_reply.status; + if (st == 0) + *psize = reply.cursor_get_record_size_reply.size; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::cursor_overwrite(Cursor *cursor, + ham_record_t *record, uint32_t flags) +{ + try { + RemoteEnvironment *env = renv(); + + SerializedWrapper request; + request.id = kCursorOverwriteRequest; + request.cursor_overwrite_request.cursor_handle = cursor->get_remote_handle(); + request.cursor_overwrite_request.flags = flags; + + if (record->size > 0) { + request.cursor_overwrite_request.record.has_data = true; + request.cursor_overwrite_request.record.data.size = record->size; + request.cursor_overwrite_request.record.data.value = (uint8_t *)record->data; + } + request.cursor_overwrite_request.record.flags = record->flags; + request.cursor_overwrite_request.record.partial_size = record->partial_size; + request.cursor_overwrite_request.record.partial_offset = record->partial_offset; + + SerializedWrapper reply; + env->perform_request(&request, &reply); + ham_assert(reply.id == kCursorOverwriteReply); + + return (reply.cursor_overwrite_reply.status); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +RemoteDatabase::cursor_move(Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + try { + RemoteEnvironment *env = renv(); + + RemoteTransaction *txn = dynamic_cast<RemoteTransaction *>(cursor->get_txn()); + ByteArray *pkey_arena = &key_arena(txn); + ByteArray *prec_arena = &record_arena(txn); + + Protocol request(Protocol::CURSOR_MOVE_REQUEST); + request.mutable_cursor_move_request()->set_cursor_handle(cursor->get_remote_handle()); + request.mutable_cursor_move_request()->set_flags(flags); + if (key) + Protocol::assign_key(request.mutable_cursor_move_request()->mutable_key(), + key, false); + if (record) + Protocol::assign_record(request.mutable_cursor_move_request()->mutable_record(), + record, false); + + ScopedPtr<Protocol> reply(env->perform_request(&request)); + + ham_assert(reply->has_cursor_move_reply() != 0); + + ham_status_t st = reply->cursor_move_reply().status(); + if (st) + return (st); + + /* modify key/record, but make sure that USER_ALLOC is respected! */ + if (reply->cursor_move_reply().has_key()) { + ham_assert(key); + key->_flags = reply->cursor_move_reply().key().intflags(); + key->size = (uint16_t)reply->cursor_move_reply().key().data().size(); + if (!(key->flags & HAM_KEY_USER_ALLOC)) { + pkey_arena->resize(key->size); + key->data = pkey_arena->get_ptr(); + } + memcpy(key->data, (void *)&reply->cursor_move_reply().key().data()[0], + key->size); + } + + /* same for the record */ + if (reply->cursor_move_reply().has_record()) { + ham_assert(record); + record->size = reply->cursor_move_reply().record().data().size(); + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + prec_arena->resize(record->size); + record->data = prec_arena->get_ptr(); + } + memcpy(record->data, (void *)&reply->cursor_move_reply().record().data()[0], + record->size); + } + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +void +RemoteDatabase::cursor_close_impl(Cursor *cursor) +{ + SerializedWrapper request; + request.id = kCursorCloseRequest; + request.cursor_close_request.cursor_handle = cursor->get_remote_handle(); + + SerializedWrapper reply; + renv()->perform_request(&request, &reply); + ham_assert(reply.id == kCursorCloseReply); +} + +ham_status_t +RemoteDatabase::close_impl(uint32_t flags) +{ + RemoteEnvironment *env = renv(); + + // do not set HAM_DONT_LOCK over the network + flags &= ~HAM_DONT_LOCK; + + Protocol request(Protocol::DB_CLOSE_REQUEST); + request.mutable_db_close_request()->set_db_handle(m_remote_handle); + request.mutable_db_close_request()->set_flags(flags); + + ScopedPtr<Protocol> reply(env->perform_request(&request)); + + ham_assert(reply->has_db_close_reply()); + + ham_status_t st = reply->db_close_reply().status(); + if (st == 0) + m_remote_handle = 0; + + return (st); +} + + +} // namespace hamsterdb + +#endif // HAM_ENABLE_REMOTE + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.h b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.h new file mode 100644 index 0000000000..1a492418bc --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4db/db_remote.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_DB_REMOTE_H +#define HAM_DB_REMOTE_H + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4db/db.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class Environment; +class RemoteEnvironment; + +/* + * The database implementation for remote file access + */ +class RemoteDatabase : public Database +{ + public: + RemoteDatabase(Environment *env, DatabaseConfiguration config, + uint64_t remote_handle) + : Database(env, config), m_remote_handle(remote_handle) { + } + + // Fills in the current metrics + virtual void fill_metrics(ham_env_metrics_t *metrics) { } + + // Returns Database parameters (ham_db_get_parameters) + virtual ham_status_t get_parameters(ham_parameter_t *param); + + // Checks Database integrity (ham_db_check_integrity) + virtual ham_status_t check_integrity(uint32_t flags); + + // Returns the number of keys + virtual ham_status_t count(Transaction *txn, bool distinct, + uint64_t *pcount); + + // Scans the whole database, applies a processor function + virtual ham_status_t scan(Transaction *txn, ScanVisitor *visitor, + bool distinct) { + return (HAM_NOT_IMPLEMENTED); + } + + // Inserts a key/value pair (ham_db_insert, ham_cursor_insert) + virtual ham_status_t insert(Cursor *cursor, Transaction *txn, + ham_key_t *key, ham_record_t *record, uint32_t flags); + + // Erase a key/value pair (ham_db_erase, ham_cursor_erase) + virtual ham_status_t erase(Cursor *cursor, Transaction *txn, ham_key_t *key, + uint32_t flags); + + // Lookup of a key/value pair (ham_db_find, ham_cursor_find) + virtual ham_status_t find(Cursor *cursor, Transaction *txn, ham_key_t *key, + ham_record_t *record, uint32_t flags); + + // Returns number of duplicates (ham_cursor_get_record_count) + virtual ham_status_t cursor_get_record_count(Cursor *cursor, uint32_t flags, + uint32_t *pcount); + + // Returns position in duplicate list (ham_cursor_get_duplicate_position) + virtual ham_status_t cursor_get_duplicate_position(Cursor *cursor, + uint32_t *pposition); + + // Get current record size (ham_cursor_get_record_size) + virtual ham_status_t cursor_get_record_size(Cursor *cursor, + uint64_t *psize); + + // Overwrites the record of a cursor (ham_cursor_overwrite) + virtual ham_status_t cursor_overwrite(Cursor *cursor, + ham_record_t *record, uint32_t flags); + + // Moves a cursor, returns key and/or record (ham_cursor_move) + virtual ham_status_t cursor_move(Cursor *cursor, ham_key_t *key, + ham_record_t *record, uint32_t flags); + + protected: + // Creates a cursor; this is the actual implementation + virtual Cursor *cursor_create_impl(Transaction *txn, uint32_t flags); + + // Clones a cursor; this is the actual implementation + virtual Cursor *cursor_clone_impl(Cursor *src); + + // Closes a cursor; this is the actual implementation + virtual void cursor_close_impl(Cursor *c); + + // Closes a database; this is the actual implementation + virtual ham_status_t close_impl(uint32_t flags); + + private: + // Returns the RemoteEnvironment instance + RemoteEnvironment *renv() { + return ((RemoteEnvironment *)m_env); + } + + // the remote database handle + uint64_t m_remote_handle; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENABLE_REMOTE */ + +#endif /* HAM_DB_REMOTE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env.cc b/plugins/Dbx_kv/src/hamsterdb/src/4env/env.cc new file mode 100644 index 0000000000..6e3a494f6d --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env.cc @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4db/db.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +namespace hamsterdb { + +ham_status_t +Environment::create() +{ + try { + return (do_create()); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::open() +{ + try { + return (do_open()); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::get_database_names(uint16_t *names, uint32_t *count) +{ + try { + ScopedLock lock(m_mutex); + return (do_get_database_names(names, count)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::get_parameters(ham_parameter_t *param) +{ + try { + ScopedLock lock(m_mutex); + return (do_get_parameters(param)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::flush(uint32_t flags) +{ + try { + ScopedLock lock(m_mutex); + return (do_flush(flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::create_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + try { + ScopedLock lock(m_mutex); + + ham_status_t st = do_create_db(pdb, config, param); + + // on success: store the open database in the environment's list of + // opened databases + if (st == 0) { + m_database_map[config.db_name] = *pdb; + /* flush the environment to make sure that the header page is written + * to disk */ + if (st == 0) + st = do_flush(0); + } + else { + if (*pdb) + (void)ham_db_close((ham_db_t *)*pdb, HAM_DONT_LOCK); + } + return (st); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::open_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + try { + ScopedLock lock(m_mutex); + + /* make sure that this database is not yet open */ + if (m_database_map.find(config.db_name) != m_database_map.end()) + return (HAM_DATABASE_ALREADY_OPEN); + + ham_status_t st = do_open_db(pdb, config, param); + + // on success: store the open database in the environment's list of + // opened databases + if (st == 0) + m_database_map[config.db_name] = *pdb; + else { + if (*pdb) + (void)ham_db_close((ham_db_t *)*pdb, HAM_DONT_LOCK); + } + return (st); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::rename_db(uint16_t oldname, uint16_t newname, uint32_t flags) +{ + try { + ScopedLock lock(m_mutex); + return (do_rename_db(oldname, newname, flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::erase_db(uint16_t dbname, uint32_t flags) +{ + try { + ScopedLock lock(m_mutex); + return (do_erase_db(dbname, flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::close_db(Database *db, uint32_t flags) +{ + ham_status_t st = 0; + + try { + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(m_mutex); + + uint16_t dbname = db->name(); + + // flush committed Transactions + st = do_flush(HAM_FLUSH_COMMITTED_TRANSACTIONS); + if (st) + return (st); + + st = db->close(flags); + if (st) + return (st); + + m_database_map.erase(dbname); + delete db; + + /* in-memory database: make sure that a database with the same name + * can be re-created */ + if (m_config.flags & HAM_IN_MEMORY) + do_erase_db(dbname, 0); + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::txn_begin(Transaction **ptxn, const char *name, uint32_t flags) +{ + try { + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(m_mutex); + + if (!(m_config.flags & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("transactions are disabled (see HAM_ENABLE_TRANSACTIONS)")); + return (HAM_INV_PARAMETER); + } + + *ptxn = do_txn_begin(name, flags); + return (0); + } + catch (Exception &ex) { + *ptxn = 0; + return (ex.code); + } +} + +std::string +Environment::txn_get_name(Transaction *txn) +{ + try { + ScopedLock lock(m_mutex); + return (txn->get_name()); + } + catch (Exception &) { + return (""); + } +} + +ham_status_t +Environment::txn_commit(Transaction *txn, uint32_t flags) +{ + try { + ScopedLock lock(m_mutex); + return (do_txn_commit(txn, flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::txn_abort(Transaction *txn, uint32_t flags) +{ + try { + ScopedLock lock(m_mutex); + return (do_txn_abort(txn, flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::close(uint32_t flags) +{ + ham_status_t st = 0; + + try { + ScopedLock lock(m_mutex); + + /* auto-abort (or commit) all pending transactions */ + if (m_txn_manager.get()) { + Transaction *t; + + while ((t = m_txn_manager->get_oldest_txn())) { + if (!t->is_aborted() && !t->is_committed()) { + if (flags & HAM_TXN_AUTO_COMMIT) + st = m_txn_manager->commit(t, 0); + else /* if (flags & HAM_TXN_AUTO_ABORT) */ + st = m_txn_manager->abort(t, 0); + if (st) + return (st); + } + + m_txn_manager->flush_committed_txns(); + } + } + + /* flush all remaining transactions */ + if (m_txn_manager) + m_txn_manager->flush_committed_txns(); + + /* close all databases */ + Environment::DatabaseMap::iterator it = m_database_map.begin(); + while (it != m_database_map.end()) { + Environment::DatabaseMap::iterator it2 = it; it++; + Database *db = it2->second; + if (flags & HAM_AUTO_CLEANUP) + st = close_db(db, flags | HAM_DONT_LOCK); + else + st = db->close(flags); + if (st) + return (st); + } + m_database_map.clear(); + + return (do_close(flags)); + } + catch (Exception &ex) { + return (ex.code); + } +} + +ham_status_t +Environment::fill_metrics(ham_env_metrics_t *metrics) +{ + try { + ScopedLock lock(m_mutex); + do_fill_metrics(metrics); + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +EnvironmentTest +Environment::test() +{ + return (EnvironmentTest(m_config)); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env.h new file mode 100644 index 0000000000..c0841151df --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: nothrow + * @thread_safe: yes + */ + +#ifndef HAM_ENV_H +#define HAM_ENV_H + +#include "0root/root.h" + +#include <map> +#include <string> + +#include "ham/hamsterdb_int.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/mutex.h" +#include "1base/scoped_ptr.h" +#include "2config/db_config.h" +#include "2config/env_config.h" +#include "4txn/txn.h" +#include "4env/env_test.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +// A helper structure; ham_env_t is declared in ham/hamsterdb.h as an +// opaque C structure, but internally we use a C++ class. The ham_env_t +// struct satisfies the C compiler, and internally we just cast the pointers. +struct ham_env_t { + int dummy; +}; + +namespace hamsterdb { + +class Database; +class Transaction; + +// +// The Environment is the "root" of all hamsterdb objects. It's a container +// for multiple databases and transactions. +// +// This class provides exception handling and locking mechanisms, then +// dispatches all calls to LocalEnvironment or RemoteEnvironment. +// +class Environment +{ + public: + // Constructor + Environment(EnvironmentConfiguration &config) + : m_config(config) { + } + + virtual ~Environment() { + } + + // Returns the flags which were set when creating/opening the Environment + uint32_t get_flags() const { + return (m_config.flags); + } + + // Returns the Environment's configuration + const EnvironmentConfiguration &config() const { + return (m_config); + } + + // Returns this Environment's mutex + Mutex &mutex() { + return (m_mutex); + } + + // Creates a new Environment (ham_env_create) + ham_status_t create(); + + // Opens a new Environment (ham_env_open) + ham_status_t open(); + + // Returns all database names (ham_env_get_database_names) + ham_status_t get_database_names(uint16_t *names, uint32_t *count); + + // Returns environment parameters and flags (ham_env_get_parameters) + ham_status_t get_parameters(ham_parameter_t *param); + + // Flushes the environment and its databases to disk (ham_env_flush) + ham_status_t flush(uint32_t flags); + + // Creates a new database in the environment (ham_env_create_db) + ham_status_t create_db(Database **db, DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Opens an existing database in the environment (ham_env_open_db) + ham_status_t open_db(Database **db, DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Renames a database in the Environment (ham_env_rename_db) + ham_status_t rename_db(uint16_t oldname, uint16_t newname, uint32_t flags); + + // Erases (deletes) a database from the Environment (ham_env_erase_db) + ham_status_t erase_db(uint16_t name, uint32_t flags); + + // Closes an existing database in the environment (ham_db_close) + ham_status_t close_db(Database *db, uint32_t flags); + + // Begins a new transaction (ham_txn_begin) + ham_status_t txn_begin(Transaction **ptxn, const char *name, + uint32_t flags); + + // Returns the name of a Transaction + std::string txn_get_name(Transaction *txn); + + // Commits a transaction (ham_txn_commit) + ham_status_t txn_commit(Transaction *txn, uint32_t flags); + + // Commits a transaction (ham_txn_abort) + ham_status_t txn_abort(Transaction *txn, uint32_t flags); + + // Closes the Environment (ham_env_close) + ham_status_t close(uint32_t flags); + + // Fills in the current metrics + ham_status_t fill_metrics(ham_env_metrics_t *metrics); + + // Returns a test object + EnvironmentTest test(); + + protected: + // Creates a new Environment (ham_env_create) + virtual ham_status_t do_create() = 0; + + // Opens a new Environment (ham_env_open) + virtual ham_status_t do_open() = 0; + + // Returns all database names (ham_env_get_database_names) + virtual ham_status_t do_get_database_names(uint16_t *names, + uint32_t *count) = 0; + + // Returns environment parameters and flags (ham_env_get_parameters) + virtual ham_status_t do_get_parameters(ham_parameter_t *param) = 0; + + // Flushes the environment and its databases to disk (ham_env_flush) + virtual ham_status_t do_flush(uint32_t flags) = 0; + + // Creates a new database in the environment (ham_env_create_db) + virtual ham_status_t do_create_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param) = 0; + + // Opens an existing database in the environment (ham_env_open_db) + virtual ham_status_t do_open_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param) = 0; + + // Renames a database in the Environment (ham_env_rename_db) + virtual ham_status_t do_rename_db(uint16_t oldname, uint16_t newname, + uint32_t flags) = 0; + + // Erases (deletes) a database from the Environment (ham_env_erase_db) + virtual ham_status_t do_erase_db(uint16_t name, uint32_t flags) = 0; + + // Begins a new transaction (ham_txn_begin) + virtual Transaction *do_txn_begin(const char *name, uint32_t flags) = 0; + + // Commits a transaction (ham_txn_commit) + virtual ham_status_t do_txn_commit(Transaction *txn, uint32_t flags) = 0; + + // Commits a transaction (ham_txn_abort) + virtual ham_status_t do_txn_abort(Transaction *txn, uint32_t flags) = 0; + + // Closes the Environment (ham_env_close) + virtual ham_status_t do_close(uint32_t flags) = 0; + + // Fills in the current metrics + virtual void do_fill_metrics(ham_env_metrics_t *metrics) const = 0; + + protected: + // A mutex to serialize access to this Environment + Mutex m_mutex; + + // The Environment's configuration + EnvironmentConfiguration m_config; + + // The Transaction manager; can be 0 + ScopedPtr<TransactionManager> m_txn_manager; + + // A map of all opened Databases + typedef std::map<uint16_t, Database *> DatabaseMap; + DatabaseMap m_database_map; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENV_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_header.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_header.h new file mode 100644 index 0000000000..56c5a5fcb8 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_header.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_ENV_HEADER_H +#define HAM_ENV_HEADER_H + +#include "0root/root.h" + +#include <map> +#include <string> + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "2page/page.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +#include "1base/packstart.h" + +/** + * the persistent file header + */ +typedef HAM_PACK_0 struct HAM_PACK_1 +{ + /** magic cookie - always "ham\0" */ + uint8_t _magic[4]; + + /** version information - major, minor, rev, file */ + uint8_t _version[4]; + + /** reserved */ + uint64_t _reserved1; + + /** size of the page */ + uint32_t _page_size; + + /** maximum number of databases for this environment */ + uint16_t _max_databases; + + /** PRO: for storing journal compression algorithm */ + uint8_t _journal_compression; + + /** reserved */ + uint8_t _reserved3; + + /** blob id of the PageManager's state */ + uint64_t _page_manager_blobid; + + /* + * following here: + * + * 1. the private data of the index btree(s) + * -> see get_btree_header() + */ +} HAM_PACK_2 PEnvironmentHeader; + +#include "1base/packstop.h" + +class EnvironmentHeader +{ + public: + // Constructor + EnvironmentHeader(Page *page) + : m_header_page(page) { + } + + // Sets the 'magic' field of a file header + void set_magic(uint8_t m1, uint8_t m2, uint8_t m3, uint8_t m4) { + get_header()->_magic[0] = m1; + get_header()->_magic[1] = m2; + get_header()->_magic[2] = m3; + get_header()->_magic[3] = m4; + } + + // Returns true if the magic matches + bool verify_magic(uint8_t m1, uint8_t m2, uint8_t m3, uint8_t m4) { + if (get_header()->_magic[0] != m1) + return (false); + if (get_header()->_magic[1] != m2) + return (false); + if (get_header()->_magic[2] != m3) + return (false); + if (get_header()->_magic[3] != m4) + return (false); + return (true); + } + + // Returns byte |i| of the 'version'-header + uint8_t get_version(int i) { + return (get_header()->_version[i]); + } + + // Sets the version of a file header + void set_version(uint8_t major, uint8_t minor, uint8_t revision, + uint8_t file) { + get_header()->_version[0] = major; + get_header()->_version[1] = minor; + get_header()->_version[2] = revision; + get_header()->_version[3] = file; + } + + // Returns get the maximum number of databases for this file + uint16_t get_max_databases() { + return (get_header()->_max_databases); + } + + // Sets the maximum number of databases for this file + void set_max_databases(uint16_t max_databases) { + get_header()->_max_databases = max_databases; + } + + // Returns the page size from the header page + uint32_t page_size() { + return (get_header()->_page_size); + } + + // Sets the page size in the header page + void set_page_size(uint32_t page_size) { + get_header()->_page_size = page_size; + } + + // Returns the PageManager's blob id + uint64_t get_page_manager_blobid() { + return (get_header()->_page_manager_blobid); + } + + // Sets the page size in the header page + void set_page_manager_blobid(uint64_t blobid) { + get_header()->_page_manager_blobid = blobid; + } + + // Returns the Journal compression configuration + int get_journal_compression(int *level) { + *level = get_header()->_journal_compression & 0x0f; + return (get_header()->_journal_compression >> 4); + } + + // Sets the Journal compression configuration + void set_journal_compression(int algorithm, int level) { + get_header()->_journal_compression = (algorithm << 4) | level; + } + + // Returns the header page with persistent configuration settings + Page *get_header_page() { + return (m_header_page); + } + + private: + // Returns a pointer to the header data + PEnvironmentHeader *get_header() { + return ((PEnvironmentHeader *)(m_header_page->get_payload())); + } + + // The header page of the Environment + Page *m_header_page; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENV_HEADER_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.cc b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.cc new file mode 100644 index 0000000000..7ba0280d7a --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.cc @@ -0,0 +1,760 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/os.h" +#include "2device/device_factory.h" +#include "3btree/btree_index.h" +#include "3btree/btree_stats.h" +#include "3blob_manager/blob_manager_factory.h" +#include "3journal/journal.h" +#include "3page_manager/page_manager.h" +#include "4db/db.h" +#include "4txn/txn.h" +#include "4txn/txn_local.h" +#include "4env/env_local.h" +#include "4cursor/cursor.h" +#include "4context/context.h" +#include "4txn/txn_cursor.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +namespace hamsterdb { + +LocalEnvironment::LocalEnvironment(EnvironmentConfiguration &config) + : Environment(config) +{ +} + +void +LocalEnvironment::recover(uint32_t flags) +{ + Context context(this); + + ham_status_t st = 0; + m_journal.reset(new Journal(this)); + + ham_assert(get_flags() & HAM_ENABLE_RECOVERY); + + try { + m_journal->open(); + } + catch (Exception &ex) { + if (ex.code == HAM_FILE_NOT_FOUND) { + m_journal->create(); + return; + } + } + + /* success - check if we need recovery */ + if (!m_journal->is_empty()) { + if (flags & HAM_AUTO_RECOVERY) { + m_journal->recover((LocalTransactionManager *)m_txn_manager.get()); + } + else { + st = HAM_NEED_RECOVERY; + goto bail; + } + } + +bail: + /* in case of errors: close log and journal, but do not delete the files */ + if (st) { + m_journal->close(true); + throw Exception(st); + } + + /* reset the page manager */ + m_page_manager->reset(&context); +} + +PBtreeHeader * +LocalEnvironment::btree_header(int i) +{ + PBtreeHeader *d = (PBtreeHeader *) + (m_header->get_header_page()->get_payload() + + sizeof(PEnvironmentHeader)); + return (d + i); +} + +LocalEnvironmentTest +LocalEnvironment::test() +{ + return (LocalEnvironmentTest(this)); +} + +ham_status_t +LocalEnvironment::do_create() +{ + if (m_config.flags & HAM_IN_MEMORY) + m_config.flags |= HAM_DISABLE_RECLAIM_INTERNAL; + + /* initialize the device if it does not yet exist */ + m_blob_manager.reset(BlobManagerFactory::create(this, m_config.flags)); + m_device.reset(DeviceFactory::create(m_config)); + if (m_config.flags & HAM_ENABLE_TRANSACTIONS) + m_txn_manager.reset(new LocalTransactionManager(this)); + + /* create the file */ + m_device->create(); + + /* allocate the header page */ + Page *page = new Page(m_device.get()); + page->alloc(Page::kTypeHeader, m_config.page_size_bytes); + ::memset(page->get_data(), 0, m_config.page_size_bytes); + page->set_type(Page::kTypeHeader); + page->set_dirty(true); + + m_header.reset(new EnvironmentHeader(page)); + + /* initialize the header */ + m_header->set_magic('H', 'A', 'M', '\0'); + m_header->set_version(HAM_VERSION_MAJ, HAM_VERSION_MIN, HAM_VERSION_REV, + HAM_FILE_VERSION); + m_header->set_page_size(m_config.page_size_bytes); + m_header->set_max_databases(m_config.max_databases); + + /* load page manager after setting up the blobmanager and the device! */ + m_page_manager.reset(new PageManager(this)); + + /* create a logfile and a journal (if requested) */ + if (get_flags() & HAM_ENABLE_RECOVERY) { + m_journal.reset(new Journal(this)); + m_journal->create(); + } + + /* flush the header page - this will write through disk if logging is + * enabled */ + if (get_flags() & HAM_ENABLE_RECOVERY) + m_header->get_header_page()->flush(); + + return (0); +} + +ham_status_t +LocalEnvironment::do_open() +{ + ham_status_t st = 0; + + Context context(this); + + /* Initialize the device if it does not yet exist. The page size will + * be filled in later (at this point in time, it's still unknown) */ + m_blob_manager.reset(BlobManagerFactory::create(this, m_config.flags)); + m_device.reset(DeviceFactory::create(m_config)); + + /* open the file */ + m_device->open(); + + if (m_config.flags & HAM_ENABLE_TRANSACTIONS) + m_txn_manager.reset(new LocalTransactionManager(this)); + + /* + * read the database header + * + * !!! + * now this is an ugly problem - the database header spans one page, but + * what's the size of this page? chances are good that it's the default + * page-size, but we really can't be sure. + * + * read 512 byte and extract the "real" page size, then read + * the real page. + */ + { + Page *page = 0; + uint8_t hdrbuf[512]; + + /* + * in here, we're going to set up a faked headerpage for the + * duration of this call; BE VERY CAREFUL: we MUST clean up + * at the end of this section or we'll be in BIG trouble! + */ + Page fakepage(m_device.get()); + fakepage.set_data((PPageData *)hdrbuf); + + /* create the configuration object */ + m_header.reset(new EnvironmentHeader(&fakepage)); + + /* + * now fetch the header data we need to get an estimate of what + * the database is made of really. + */ + m_device->read(0, hdrbuf, sizeof(hdrbuf)); + + m_config.page_size_bytes = m_header->page_size(); + + /** check the file magic */ + if (!m_header->verify_magic('H', 'A', 'M', '\0')) { + ham_log(("invalid file type")); + st = HAM_INV_FILE_HEADER; + goto fail_with_fake_cleansing; + } + + /* check the database version; everything with a different file version + * is incompatible */ + if (m_header->get_version(3) != HAM_FILE_VERSION) { + ham_log(("invalid file version")); + st = HAM_INV_FILE_VERSION; + goto fail_with_fake_cleansing; + } + else if (m_header->get_version(0) == 1 && + m_header->get_version(1) == 0 && + m_header->get_version(2) <= 9) { + ham_log(("invalid file version; < 1.0.9 is not supported")); + st = HAM_INV_FILE_VERSION; + goto fail_with_fake_cleansing; + } + + st = 0; + +fail_with_fake_cleansing: + + /* undo the headerpage fake first! */ + fakepage.set_data(0); + m_header.reset(0); + + /* exit when an error was signaled */ + if (st) { + if (m_device->is_open()) + m_device->close(); + return (st); + } + + /* now read the "real" header page and store it in the Environment */ + page = new Page(m_device.get()); + page->fetch(0); + m_header.reset(new EnvironmentHeader(page)); + } + + /* load page manager after setting up the blobmanager and the device! */ + m_page_manager.reset(new PageManager(this)); + + /* check if recovery is required */ + if (get_flags() & HAM_ENABLE_RECOVERY) + recover(m_config.flags); + + /* load the state of the PageManager */ + if (m_header->get_page_manager_blobid() != 0) + m_page_manager->initialize(m_header->get_page_manager_blobid()); + + return (0); +} + +ham_status_t +LocalEnvironment::do_get_database_names(uint16_t *names, uint32_t *count) +{ + uint16_t name; + uint32_t i = 0; + uint32_t max_names = 0; + + max_names = *count; + *count = 0; + + /* copy each database name to the array */ + ham_assert(m_header->get_max_databases() > 0); + for (i = 0; i < m_header->get_max_databases(); i++) { + name = btree_header(i)->get_dbname(); + if (name == 0) + continue; + + if (*count >= max_names) + return (HAM_LIMITS_REACHED); + + names[(*count)++] = name; + } + + return 0; +} + +ham_status_t +LocalEnvironment::do_get_parameters(ham_parameter_t *param) +{ + ham_parameter_t *p = param; + + if (p) { + for (; p->name; p++) { + switch (p->name) { + case HAM_PARAM_CACHE_SIZE: + p->value = m_config.cache_size_bytes; + break; + case HAM_PARAM_PAGE_SIZE: + p->value = m_config.page_size_bytes; + break; + case HAM_PARAM_MAX_DATABASES: + p->value = m_header->get_max_databases(); + break; + case HAM_PARAM_FLAGS: + p->value = get_flags(); + break; + case HAM_PARAM_FILEMODE: + p->value = m_config.file_mode; + break; + case HAM_PARAM_FILENAME: + if (m_config.filename.size()) + p->value = (uint64_t)(PTR_TO_U64(m_config.filename.c_str())); + else + p->value = 0; + break; + case HAM_PARAM_LOG_DIRECTORY: + if (m_config.log_filename.size()) + p->value = (uint64_t)(PTR_TO_U64(m_config.log_filename.c_str())); + else + p->value = 0; + break; + case HAM_PARAM_JOURNAL_SWITCH_THRESHOLD: + p->value = m_config.journal_switch_threshold; + break; + case HAM_PARAM_JOURNAL_COMPRESSION: + p->value = 0; + break; + case HAM_PARAM_POSIX_FADVISE: + p->value = m_config.posix_advice; + break; + default: + ham_trace(("unknown parameter %d", (int)p->name)); + return (HAM_INV_PARAMETER); + } + } + } + + return (0); +} + +ham_status_t +LocalEnvironment::do_flush(uint32_t flags) +{ + Context context(this, 0, 0); + + /* flush all committed transactions */ + if (m_txn_manager) + m_txn_manager->flush_committed_txns(&context); + + if (flags & HAM_FLUSH_COMMITTED_TRANSACTIONS || get_flags() & HAM_IN_MEMORY) + return (0); + + /* flush the header page */ + m_header->get_header_page()->flush(); + + /* flush all open pages to disk */ + m_page_manager->flush(false); + + /* flush the device - this usually causes a fsync() */ + m_device->flush(); + return (0); +} + +ham_status_t +LocalEnvironment::do_create_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + if (get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot create database in a read-only environment")); + return (HAM_WRITE_PROTECTED); + } + + if (param) { + for (; param->name; param++) { + switch (param->name) { + case HAM_PARAM_RECORD_COMPRESSION: + ham_trace(("Record compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_KEY_COMPRESSION: + ham_trace(("Key compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_KEY_TYPE: + config.key_type = (uint16_t)param->value; + break; + case HAM_PARAM_KEY_SIZE: + if (param->value != 0) { + if (param->value > 0xffff) { + ham_trace(("invalid key size %u - must be < 0xffff")); + return (HAM_INV_KEY_SIZE); + } + if (config.flags & HAM_RECORD_NUMBER32) { + if (param->value > 0 && param->value != sizeof(uint32_t)) { + ham_trace(("invalid key size %u - must be 4 for " + "HAM_RECORD_NUMBER32 databases", + (unsigned)param->value)); + return (HAM_INV_KEY_SIZE); + } + } + if (config.flags & HAM_RECORD_NUMBER64) { + if (param->value > 0 && param->value != sizeof(uint64_t)) { + ham_trace(("invalid key size %u - must be 8 for " + "HAM_RECORD_NUMBER64 databases", + (unsigned)param->value)); + return (HAM_INV_KEY_SIZE); + } + } + config.key_size = (uint16_t)param->value; + } + break; + case HAM_PARAM_RECORD_SIZE: + config.record_size = (uint32_t)param->value; + break; + default: + ham_trace(("invalid parameter 0x%x (%d)", param->name, param->name)); + return (HAM_INV_PARAMETER); + } + } + } + + if (config.flags & HAM_RECORD_NUMBER32) { + if (config.key_type == HAM_TYPE_UINT8 + || config.key_type == HAM_TYPE_UINT16 + || config.key_type == HAM_TYPE_UINT64 + || config.key_type == HAM_TYPE_REAL32 + || config.key_type == HAM_TYPE_REAL64) { + ham_trace(("HAM_RECORD_NUMBER32 not allowed in combination with " + "fixed length type")); + return (HAM_INV_PARAMETER); + } + config.key_type = HAM_TYPE_UINT32; + } + else if (config.flags & HAM_RECORD_NUMBER64) { + if (config.key_type == HAM_TYPE_UINT8 + || config.key_type == HAM_TYPE_UINT16 + || config.key_type == HAM_TYPE_UINT32 + || config.key_type == HAM_TYPE_REAL32 + || config.key_type == HAM_TYPE_REAL64) { + ham_trace(("HAM_RECORD_NUMBER64 not allowed in combination with " + "fixed length type")); + return (HAM_INV_PARAMETER); + } + config.key_type = HAM_TYPE_UINT64; + } + + uint32_t mask = HAM_FORCE_RECORDS_INLINE + | HAM_FLUSH_WHEN_COMMITTED + | HAM_ENABLE_DUPLICATE_KEYS + | HAM_RECORD_NUMBER32 + | HAM_RECORD_NUMBER64; + if (config.flags & ~mask) { + ham_trace(("invalid flags(s) 0x%x", config.flags & ~mask)); + return (HAM_INV_PARAMETER); + } + + /* create a new Database object */ + LocalDatabase *db = new LocalDatabase(this, config); + + Context context(this, 0, db); + + /* check if this database name is unique */ + uint16_t dbi; + for (uint32_t i = 0; i < m_header->get_max_databases(); i++) { + uint16_t name = btree_header(i)->get_dbname(); + if (!name) + continue; + if (name == config.db_name) { + delete db; + return (HAM_DATABASE_ALREADY_EXISTS); + } + } + + /* find a free slot in the PBtreeHeader array and store the name */ + for (dbi = 0; dbi < m_header->get_max_databases(); dbi++) { + uint16_t name = btree_header(dbi)->get_dbname(); + if (!name) { + btree_header(dbi)->set_dbname(config.db_name); + break; + } + } + if (dbi == m_header->get_max_databases()) { + delete db; + return (HAM_LIMITS_REACHED); + } + + mark_header_page_dirty(&context); + + /* initialize the Database */ + ham_status_t st = db->create(&context, btree_header(dbi)); + if (st) { + delete db; + return (st); + } + + /* force-flush the changeset */ + if (get_flags() & HAM_ENABLE_RECOVERY) + context.changeset.flush(next_lsn()); + + *pdb = db; + return (0); +} + +ham_status_t +LocalEnvironment::do_open_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + *pdb = 0; + + uint32_t mask = HAM_FORCE_RECORDS_INLINE + | HAM_FLUSH_WHEN_COMMITTED + | HAM_READ_ONLY; + if (config.flags & ~mask) { + ham_trace(("invalid flags(s) 0x%x", config.flags & ~mask)); + return (HAM_INV_PARAMETER); + } + + if (param) { + for (; param->name; param++) { + switch (param->name) { + case HAM_PARAM_RECORD_COMPRESSION: + ham_trace(("Record compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_KEY_COMPRESSION: + ham_trace(("Key compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + default: + ham_trace(("invalid parameter 0x%x (%d)", param->name, param->name)); + return (HAM_INV_PARAMETER); + } + } + } + + /* create a new Database object */ + LocalDatabase *db = new LocalDatabase(this, config); + + Context context(this, 0, db); + + ham_assert(0 != m_header->get_header_page()); + + /* search for a database with this name */ + uint16_t dbi; + for (dbi = 0; dbi < m_header->get_max_databases(); dbi++) { + uint16_t name = btree_header(dbi)->get_dbname(); + if (!name) + continue; + if (config.db_name == name) + break; + } + + if (dbi == m_header->get_max_databases()) { + delete db; + return (HAM_DATABASE_NOT_FOUND); + } + + /* open the database */ + ham_status_t st = db->open(&context, btree_header(dbi)); + if (st) { + delete db; + ham_trace(("Database could not be opened")); + return (st); + } + + *pdb = db; + return (0); +} + +ham_status_t +LocalEnvironment::do_rename_db(uint16_t oldname, uint16_t newname, + uint32_t flags) +{ + Context context(this); + + /* + * check if a database with the new name already exists; also search + * for the database with the old name + */ + uint16_t max = m_header->get_max_databases(); + uint16_t slot = max; + ham_assert(max > 0); + for (uint16_t dbi = 0; dbi < max; dbi++) { + uint16_t name = btree_header(dbi)->get_dbname(); + if (name == newname) + return (HAM_DATABASE_ALREADY_EXISTS); + if (name == oldname) + slot = dbi; + } + + if (slot == max) + return (HAM_DATABASE_NOT_FOUND); + + /* replace the database name with the new name */ + btree_header(slot)->set_dbname(newname); + mark_header_page_dirty(&context); + + /* if the database with the old name is currently open: notify it */ + Environment::DatabaseMap::iterator it = m_database_map.find(oldname); + if (it != m_database_map.end()) { + Database *db = it->second; + it->second->set_name(newname); + m_database_map.erase(oldname); + m_database_map.insert(DatabaseMap::value_type(newname, db)); + } + + return (0); +} + +ham_status_t +LocalEnvironment::do_erase_db(uint16_t name, uint32_t flags) +{ + /* check if this database is still open */ + if (m_database_map.find(name) != m_database_map.end()) + return (HAM_DATABASE_ALREADY_OPEN); + + /* + * if it's an in-memory environment then it's enough to purge the + * database from the environment header + */ + if (get_flags() & HAM_IN_MEMORY) { + for (uint16_t dbi = 0; dbi < m_header->get_max_databases(); dbi++) { + PBtreeHeader *desc = btree_header(dbi); + if (name == desc->get_dbname()) { + desc->set_dbname(0); + return (0); + } + } + return (HAM_DATABASE_NOT_FOUND); + } + + /* temporarily load the database */ + LocalDatabase *db; + DatabaseConfiguration config; + config.db_name = name; + ham_status_t st = do_open_db((Database **)&db, config, 0); + if (st) + return (st); + + Context context(this, 0, db); + + /* + * delete all blobs and extended keys, also from the cache and + * the extkey-cache + * + * also delete all pages and move them to the freelist; if they're + * cached, delete them from the cache + */ + st = db->drop(&context); + if (st) + return (st); + + /* now set database name to 0 and set the header page to dirty */ + for (uint16_t dbi = 0; dbi < m_header->get_max_databases(); dbi++) { + PBtreeHeader *desc = btree_header(dbi); + if (name == desc->get_dbname()) { + desc->set_dbname(0); + break; + } + } + + mark_header_page_dirty(&context); + context.changeset.clear(); + + (void)ham_db_close((ham_db_t *)db, HAM_DONT_LOCK); + + return (0); +} + +Transaction * +LocalEnvironment::do_txn_begin(const char *name, uint32_t flags) +{ + Transaction *txn = new LocalTransaction(this, name, flags); + m_txn_manager->begin(txn); + return (txn); +} + +ham_status_t +LocalEnvironment::do_txn_commit(Transaction *txn, uint32_t flags) +{ + return (m_txn_manager->commit(txn, flags)); +} + +ham_status_t +LocalEnvironment::do_txn_abort(Transaction *txn, uint32_t flags) +{ + return (m_txn_manager->abort(txn, flags)); +} + +ham_status_t +LocalEnvironment::do_close(uint32_t flags) +{ + Context context(this); + + /* flush all committed transactions */ + if (m_txn_manager) + m_txn_manager->flush_committed_txns(&context); + + /* flush all pages and the freelist, reduce the file size */ + if (m_page_manager) + m_page_manager->close(&context); + + /* if we're not in read-only mode, and not an in-memory-database, + * and the dirty-flag is true: flush the page-header to disk */ + if (m_header && m_header->get_header_page() && !(get_flags() & HAM_IN_MEMORY) + && m_device.get() && m_device.get()->is_open() + && (!(get_flags() & HAM_READ_ONLY))) { + m_header->get_header_page()->flush(); + } + + /* close the header page */ + if (m_header && m_header->get_header_page()) { + Page *page = m_header->get_header_page(); + if (page->get_data()) + m_device->free_page(page); + delete page; + m_header.reset(); + } + + /* close the device */ + if (m_device) { + if (m_device->is_open()) { + if (!(get_flags() & HAM_READ_ONLY)) + m_device->flush(); + m_device->close(); + } + } + + /* close the log and the journal */ + if (m_journal) + m_journal->close(!!(flags & HAM_DONT_CLEAR_LOG)); + + return (0); +} + +void +LocalEnvironment::do_fill_metrics(ham_env_metrics_t *metrics) const +{ + // PageManager metrics (incl. cache and freelist) + m_page_manager->fill_metrics(metrics); + // the BlobManagers + m_blob_manager->fill_metrics(metrics); + // the Journal (if available) + if (m_journal) + m_journal->fill_metrics(metrics); + // the (first) database + if (!m_database_map.empty()) { + LocalDatabase *db = (LocalDatabase *)m_database_map.begin()->second; + db->fill_metrics(metrics); + } + // and of the btrees + BtreeIndex::fill_metrics(metrics); + // SIMD support enabled? + metrics->simd_lane_width = os_get_simd_lane_width(); +} + +void +LocalEnvironmentTest::set_journal(Journal *journal) +{ + m_env->m_journal.reset(journal); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.h new file mode 100644 index 0000000000..7800ee37de --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_ENV_LOCAL_H +#define HAM_ENV_LOCAL_H + +#include "ham/hamsterdb.h" + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/scoped_ptr.h" +#include "2lsn_manager/lsn_manager.h" +#include "3journal/journal.h" +#include "4env/env.h" +#include "4env/env_header.h" +#include "4env/env_local_test.h" +#include "4context/context.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class PBtreeHeader; +class PFreelistPayload; +class Journal; +class PageManager; +class BlobManager; +class LocalTransaction; +struct MessageBase; + +// +// The Environment implementation for local file access +// +class LocalEnvironment : public Environment +{ + public: + LocalEnvironment(EnvironmentConfiguration &config); + + // Returns the Device object + Device *device() { + return (m_device.get()); + } + + // Returns the Environment's header object with the persistent configuration + EnvironmentHeader *header() { + return (m_header.get()); + } + + // Returns the blob manager + BlobManager *blob_manager() { + return (m_blob_manager.get()); + } + + // Returns the PageManager instance + PageManager *page_manager() { + return (m_page_manager.get()); + } + + // Returns the Journal + Journal *journal() { + return (m_journal.get()); + } + + // Returns the lsn manager + LsnManager *lsn_manager() { + return (&m_lsn_manager); + } + + // The transaction manager + TransactionManager *txn_manager() { + return (m_txn_manager.get()); + } + + // Increments the lsn and returns the incremented value + uint64_t next_lsn() { + return (m_lsn_manager.next()); + } + + // Returns a test gateway + LocalEnvironmentTest test(); + + protected: + // Creates a new Environment (ham_env_create) + virtual ham_status_t do_create(); + + // Opens a new Environment (ham_env_open) + virtual ham_status_t do_open(); + + // Returns all database names (ham_env_get_database_names) + virtual ham_status_t do_get_database_names(uint16_t *names, + uint32_t *count); + + // Returns environment parameters and flags (ham_env_get_parameters) + virtual ham_status_t do_get_parameters(ham_parameter_t *param); + + // Flushes the environment and its databases to disk (ham_env_flush) + virtual ham_status_t do_flush(uint32_t flags); + + // Creates a new database in the environment (ham_env_create_db) + virtual ham_status_t do_create_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Opens an existing database in the environment (ham_env_open_db) + virtual ham_status_t do_open_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Renames a database in the Environment (ham_env_rename_db) + virtual ham_status_t do_rename_db(uint16_t oldname, uint16_t newname, + uint32_t flags); + + // Erases (deletes) a database from the Environment (ham_env_erase_db) + virtual ham_status_t do_erase_db(uint16_t name, uint32_t flags); + + // Begins a new transaction (ham_txn_begin) + virtual Transaction *do_txn_begin(const char *name, uint32_t flags); + + // Commits a transaction (ham_txn_commit) + virtual ham_status_t do_txn_commit(Transaction *txn, uint32_t flags); + + // Commits a transaction (ham_txn_abort) + virtual ham_status_t do_txn_abort(Transaction *txn, uint32_t flags); + + // Closes the Environment (ham_env_close) + virtual ham_status_t do_close(uint32_t flags); + + // Fills in the current metrics + virtual void do_fill_metrics(ham_env_metrics_t *metrics) const; + + private: + friend class LocalEnvironmentTest; + + // Runs the recovery process + void recover(uint32_t flags); + + // Get the btree configuration of the database #i, where |i| is a + // zero-based index + PBtreeHeader *btree_header(int i); + + // Sets the dirty-flag of the header page and adds the header page + // to the Changeset (if recovery is enabled) + void mark_header_page_dirty(Context *context) { + Page *page = m_header->get_header_page(); + page->set_dirty(true); + if (get_flags() & HAM_ENABLE_RECOVERY) + context->changeset.put(page); + } + + // The Environment's header page/configuration + ScopedPtr<EnvironmentHeader> m_header; + + // The device instance (either a file or an in-memory-db) + ScopedPtr<Device> m_device; + + // The BlobManager instance + ScopedPtr<BlobManager> m_blob_manager; + + // The PageManager instance + ScopedPtr<PageManager> m_page_manager; + + // The logical journal + ScopedPtr<Journal> m_journal; + + // The lsn manager + LsnManager m_lsn_manager; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENV_LOCAL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local_test.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local_test.h new file mode 100644 index 0000000000..ea045e18dc --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_local_test.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: no + * @thread_safe: no + */ + +#ifndef HAM_ENV_LOCAL_TEST_H +#define HAM_ENV_LOCAL_TEST_H + +#include "ham/hamsterdb.h" + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Journal; +class LocalEnvironment; + +class LocalEnvironmentTest +{ + public: + LocalEnvironmentTest(LocalEnvironment *env) + : m_env(env) { + } + + // Sets a new journal object + void set_journal(Journal *journal); + + private: + LocalEnvironment *m_env; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENV_LOCAL_TEST_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.cc b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.cc new file mode 100644 index 0000000000..6e53543c8b --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.cc @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/os.h" +#include "1base/scoped_ptr.h" +#include "2protobuf/protocol.h" +#include "4cursor/cursor.h" +#include "4db/db_remote.h" +#include "4env/env_remote.h" +#include "4txn/txn_remote.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +RemoteEnvironment::RemoteEnvironment(EnvironmentConfiguration config) + : Environment(config), m_remote_handle(0), m_buffer(1024 * 4) +{ +} + +Protocol * +RemoteEnvironment::perform_request(Protocol *request) +{ + // use ByteArray to avoid frequent reallocs! + m_buffer.clear(); + + if (!request->pack(&m_buffer)) { + ham_log(("protoype Protocol::pack failed")); + throw Exception(HAM_INTERNAL_ERROR); + } + + m_socket.send((uint8_t *)m_buffer.get_ptr(), m_buffer.get_size()); + + // now block and wait for the reply; first read the header, then the + // remaining data + m_socket.recv((uint8_t *)m_buffer.get_ptr(), 8); + + // no need to check the magic; it's verified in Protocol::unpack + uint32_t size = *(uint32_t *)((char *)m_buffer.get_ptr() + 4); + m_buffer.resize(size + 8); + m_socket.recv((uint8_t *)m_buffer.get_ptr() + 8, size); + + return (Protocol::unpack((const uint8_t *)m_buffer.get_ptr(), size + 8)); +} + +void +RemoteEnvironment::perform_request(SerializedWrapper *request, + SerializedWrapper *reply) +{ + int size_left = (int)request->get_size(); + request->size = size_left; + request->magic = HAM_TRANSFER_MAGIC_V2; + m_buffer.resize(request->size); + + uint8_t *ptr = (uint8_t *)m_buffer.get_ptr(); + request->serialize(&ptr, &size_left); + ham_assert(size_left == 0); + + m_socket.send((uint8_t *)m_buffer.get_ptr(), request->size); + + // now block and wait for the reply; first read the header, then the + // remaining data + m_socket.recv((uint8_t *)m_buffer.get_ptr(), 8); + + // now check the magic and receive the remaining data + uint32_t magic = *(uint32_t *)((char *)m_buffer.get_ptr() + 0); + if (magic != HAM_TRANSFER_MAGIC_V2) + throw Exception(HAM_INTERNAL_ERROR); + // TODO check the magic + int size = (int)*(uint32_t *)((char *)m_buffer.get_ptr() + 4); + m_buffer.resize(size); + m_socket.recv((uint8_t *)m_buffer.get_ptr() + 8, size - 8); + + ptr = (uint8_t *)m_buffer.get_ptr(); + reply->deserialize(&ptr, &size); + ham_assert(size == 0); +} + +ham_status_t +RemoteEnvironment::do_create() +{ + // the 'create' operation is identical to 'open' + return (do_open()); +} + +ham_status_t +RemoteEnvironment::do_open() +{ + m_socket.close(); + + const char *url = m_config.filename.c_str(); + + ham_assert(url != 0); + ham_assert(::strstr(url, "ham://") == url); + const char *ip = url + 6; + const char *port_str = strstr(ip, ":"); + if (!port_str) { + ham_trace(("remote uri does not include port - expected " + "`ham://<ip>:<port>`")); + return (HAM_INV_PARAMETER); + } + uint16_t port = (uint16_t)atoi(port_str + 1); + if (!port) { + ham_trace(("remote uri includes invalid port - expected " + "`ham://<ip>:<port>`")); + return (HAM_INV_PARAMETER); + } + + const char *filename = strstr(port_str, "/"); + + std::string hostname(ip, port_str); + m_socket.connect(hostname.c_str(), port, m_config.remote_timeout_sec); + + Protocol request(Protocol::CONNECT_REQUEST); + request.mutable_connect_request()->set_path(filename); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->type() == Protocol::CONNECT_REPLY); + + ham_status_t st = reply->connect_reply().status(); + if (st == 0) { + m_config.flags |= reply->connect_reply().env_flags(); + m_remote_handle = reply->connect_reply().env_handle(); + + if (get_flags() & HAM_ENABLE_TRANSACTIONS) + m_txn_manager.reset(new RemoteTransactionManager(this)); + } + + return (st); +} + +ham_status_t +RemoteEnvironment::do_get_database_names(uint16_t *names, uint32_t *count) +{ + Protocol request(Protocol::ENV_GET_DATABASE_NAMES_REQUEST); + request.mutable_env_get_database_names_request(); + request.mutable_env_get_database_names_request()->set_env_handle(m_remote_handle); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_get_database_names_reply()); + + ham_status_t st = reply->env_get_database_names_reply().status(); + if (st) + return (st); + + /* copy the retrieved names */ + uint32_t i; + for (i = 0; + i < (uint32_t)reply->env_get_database_names_reply().names_size() + && i < *count; + i++) { + names[i] = (uint16_t)*(reply->mutable_env_get_database_names_reply()->mutable_names()->mutable_data() + i); + } + + *count = i; + return (0); +} + +ham_status_t +RemoteEnvironment::do_get_parameters(ham_parameter_t *param) +{ + static char filename[1024]; // TODO not threadsafe!! + ham_parameter_t *p = param; + + Protocol request(Protocol::ENV_GET_PARAMETERS_REQUEST); + request.mutable_env_get_parameters_request()->set_env_handle(m_remote_handle); + while (p && p->name != 0) { + request.mutable_env_get_parameters_request()->add_names(p->name); + p++; + } + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_get_parameters_reply()); + + ham_status_t st = reply->env_get_parameters_reply().status(); + if (st) + return (st); + + p = param; + while (p && p->name) { + switch (p->name) { + case HAM_PARAM_CACHESIZE: + ham_assert(reply->env_get_parameters_reply().has_cache_size()); + p->value = reply->env_get_parameters_reply().cache_size(); + break; + case HAM_PARAM_PAGESIZE: + ham_assert(reply->env_get_parameters_reply().has_page_size()); + p->value = reply->env_get_parameters_reply().page_size(); + break; + case HAM_PARAM_MAX_DATABASES: + ham_assert(reply->env_get_parameters_reply().has_max_env_databases()); + p->value = reply->env_get_parameters_reply().max_env_databases(); + break; + case HAM_PARAM_FLAGS: + ham_assert(reply->env_get_parameters_reply().has_flags()); + p->value = reply->env_get_parameters_reply().flags(); + break; + case HAM_PARAM_FILEMODE: + ham_assert(reply->env_get_parameters_reply().has_filemode()); + p->value = reply->env_get_parameters_reply().filemode(); + break; + case HAM_PARAM_FILENAME: + if (reply->env_get_parameters_reply().has_filename()) { + strncpy(filename, reply->env_get_parameters_reply().filename().c_str(), + sizeof(filename) - 1); + filename[sizeof(filename) - 1] = 0; + p->value = (uint64_t)(&filename[0]); + } + break; + default: + ham_trace(("unknown parameter %d", (int)p->name)); + break; + } + p++; + } + return (0); +} + +ham_status_t +RemoteEnvironment::do_flush(uint32_t flags) +{ + Protocol request(Protocol::ENV_FLUSH_REQUEST); + request.mutable_env_flush_request()->set_flags(flags); + request.mutable_env_flush_request()->set_env_handle(m_remote_handle); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_flush_reply()); + + return (reply->env_flush_reply().status()); +} + +ham_status_t +RemoteEnvironment::do_create_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + Protocol request(Protocol::ENV_CREATE_DB_REQUEST); + request.mutable_env_create_db_request()->set_env_handle(m_remote_handle); + request.mutable_env_create_db_request()->set_dbname(config.db_name); + request.mutable_env_create_db_request()->set_flags(config.flags); + + const ham_parameter_t *p = param; + if (p) { + for (; p->name; p++) { + request.mutable_env_create_db_request()->add_param_names(p->name); + request.mutable_env_create_db_request()->add_param_values(p->value); + } + } + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_create_db_reply()); + + ham_status_t st = reply->env_create_db_reply().status(); + if (st) + return (st); + + config.flags = reply->env_create_db_reply().db_flags(); + RemoteDatabase *rdb = new RemoteDatabase(this, config, + reply->env_create_db_reply().db_handle()); + + *pdb = rdb; + return (0); +} + +ham_status_t +RemoteEnvironment::do_open_db(Database **pdb, DatabaseConfiguration &config, + const ham_parameter_t *param) +{ + Protocol request(Protocol::ENV_OPEN_DB_REQUEST); + request.mutable_env_open_db_request()->set_env_handle(m_remote_handle); + request.mutable_env_open_db_request()->set_dbname(config.db_name); + request.mutable_env_open_db_request()->set_flags(config.flags); + + const ham_parameter_t *p = param; + if (p) { + for (; p->name; p++) { + request.mutable_env_open_db_request()->add_param_names(p->name); + request.mutable_env_open_db_request()->add_param_values(p->value); + } + } + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_open_db_reply()); + + ham_status_t st = reply->env_open_db_reply().status(); + if (st) + return (st); + + config.flags = reply->env_open_db_reply().db_flags(); + RemoteDatabase *rdb = new RemoteDatabase(this, config, + reply->env_open_db_reply().db_handle()); + + *pdb = rdb; + return (0); +} + +ham_status_t +RemoteEnvironment::do_rename_db( uint16_t oldname, uint16_t newname, + uint32_t flags) +{ + Protocol request(Protocol::ENV_RENAME_REQUEST); + request.mutable_env_rename_request()->set_env_handle(m_remote_handle); + request.mutable_env_rename_request()->set_oldname(oldname); + request.mutable_env_rename_request()->set_newname(newname); + request.mutable_env_rename_request()->set_flags(flags); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_rename_reply()); + + return (reply->env_rename_reply().status()); +} + +ham_status_t +RemoteEnvironment::do_erase_db(uint16_t name, uint32_t flags) +{ + Protocol request(Protocol::ENV_ERASE_DB_REQUEST); + request.mutable_env_erase_db_request()->set_env_handle(m_remote_handle); + request.mutable_env_erase_db_request()->set_name(name); + request.mutable_env_erase_db_request()->set_flags(flags); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + ham_assert(reply->has_env_erase_db_reply()); + + return (reply->env_erase_db_reply().status()); +} + +Transaction * +RemoteEnvironment::do_txn_begin(const char *name, uint32_t flags) +{ + SerializedWrapper request; + request.id = kTxnBeginRequest; + request.txn_begin_request.env_handle = m_remote_handle; + request.txn_begin_request.flags = flags; + if (name) { + request.txn_begin_request.name.value = (uint8_t *)name; + request.txn_begin_request.name.size = strlen(name) + 1; + } + + SerializedWrapper reply; + perform_request(&request, &reply); + ham_assert(reply.id == kTxnBeginReply); + + ham_status_t st = reply.txn_begin_reply.status; + if (st) + throw Exception(st); + + Transaction *txn = new RemoteTransaction(this, name, flags, + reply.txn_begin_reply.txn_handle); + m_txn_manager->begin(txn); + return (txn); +} + +ham_status_t +RemoteEnvironment::do_txn_commit(Transaction *txn, uint32_t flags) +{ + RemoteTransaction *rtxn = dynamic_cast<RemoteTransaction *>(txn); + + SerializedWrapper request; + request.id = kTxnCommitRequest; + request.txn_commit_request.txn_handle = rtxn->get_remote_handle(); + request.txn_commit_request.flags = flags; + + SerializedWrapper reply; + perform_request(&request, &reply); + ham_assert(reply.id == kTxnCommitReply); + + ham_status_t st = reply.txn_commit_reply.status; + if (st) + return (st); + + return (m_txn_manager->commit(txn, flags)); +} + +ham_status_t +RemoteEnvironment::do_txn_abort(Transaction *txn, uint32_t flags) +{ + RemoteTransaction *rtxn = dynamic_cast<RemoteTransaction *>(txn); + + SerializedWrapper request; + request.id = kTxnAbortRequest; + request.txn_abort_request.txn_handle = rtxn->get_remote_handle(); + request.txn_abort_request.flags = flags; + + SerializedWrapper reply; + perform_request(&request, &reply); + ham_assert(reply.id == kTxnAbortReply); + ham_status_t st = reply.txn_abort_reply.status; + if (st) + return (st); + + return (m_txn_manager->abort(txn, flags)); +} + +ham_status_t +RemoteEnvironment::do_close(uint32_t flags) +{ + Protocol request(Protocol::DISCONNECT_REQUEST); + request.mutable_disconnect_request()->set_env_handle(m_remote_handle); + + ScopedPtr<Protocol> reply(perform_request(&request)); + + // ignore the reply + + m_socket.close(); + m_remote_handle = 0; + return (0); +} + +void +RemoteEnvironment::do_fill_metrics(ham_env_metrics_t *metrics) const +{ + throw Exception(HAM_NOT_IMPLEMENTED); +} + +} // namespace hamsterdb + +#endif // HAM_ENABLE_REMOTE + diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.h new file mode 100644 index 0000000000..c45fd5b222 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_remote.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_ENV_REMOTE_H +#define HAM_ENV_REMOTE_H + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "1os/socket.h" +#include "1base/dynamic_array.h" +#include "2protobuf/protocol.h" +#include "2protoserde/messages.h" +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +// +// The Environment implementation for remote file access +// +class RemoteEnvironment : public Environment +{ + public: + // Constructor + RemoteEnvironment(EnvironmentConfiguration config); + + // Sends a |request| message with the Google Protocol Buffers API. Blocks + // till the reply was fully received. Returns the reply structure. + Protocol *perform_request(Protocol *request); + + // Sends |request| message with the builtin Serde API. Blocks till the + // reply was fully received. Fills |reply| with the received data. + void perform_request(SerializedWrapper *request, SerializedWrapper *reply); + + protected: + // Creates a new Environment (ham_env_create) + virtual ham_status_t do_create(); + + // Opens a new Environment (ham_env_open) + virtual ham_status_t do_open(); + + // Returns all database names (ham_env_get_database_names) + virtual ham_status_t do_get_database_names(uint16_t *names, + uint32_t *count); + + // Returns environment parameters and flags (ham_env_get_parameters) + virtual ham_status_t do_get_parameters(ham_parameter_t *param); + + // Flushes the environment and its databases to disk (ham_env_flush) + virtual ham_status_t do_flush(uint32_t flags); + + // Creates a new database in the environment (ham_env_create_db) + virtual ham_status_t do_create_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Opens an existing database in the environment (ham_env_open_db) + virtual ham_status_t do_open_db(Database **db, + DatabaseConfiguration &config, + const ham_parameter_t *param); + + // Renames a database in the Environment (ham_env_rename_db) + virtual ham_status_t do_rename_db(uint16_t oldname, uint16_t newname, + uint32_t flags); + + // Erases (deletes) a database from the Environment (ham_env_erase_db) + virtual ham_status_t do_erase_db(uint16_t name, uint32_t flags); + + // Begins a new transaction (ham_txn_begin) + virtual Transaction *do_txn_begin(const char *name, uint32_t flags); + + // Commits a transaction (ham_txn_commit) + virtual ham_status_t do_txn_commit(Transaction *txn, uint32_t flags); + + // Commits a transaction (ham_txn_abort) + virtual ham_status_t do_txn_abort(Transaction *txn, uint32_t flags); + + // Closes the Environment (ham_env_close) + virtual ham_status_t do_close(uint32_t flags); + + // Fills in the current metrics + virtual void do_fill_metrics(ham_env_metrics_t *metrics) const; + + private: + // the remote handle + uint64_t m_remote_handle; + + // the socket + Socket m_socket; + + // a buffer to avoid frequent memory allocations + ByteArray m_buffer; +}; + +} // namespace hamsterdb + +#endif // HAM_ENABLE_REMOTE + +#endif /* HAM_ENV_REMOTE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4env/env_test.h b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_test.h new file mode 100644 index 0000000000..0d9fa76cec --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4env/env_test.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: no + * @thread_safe: no + */ + +#ifndef HAM_ENV_TEST_H +#define HAM_ENV_TEST_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4env/env.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class EnvironmentTest +{ + public: + // Constructor + EnvironmentTest(EnvironmentConfiguration &config) + : m_config(config) { + } + + // Returns the Environment's configuration + EnvironmentConfiguration &config() { + return (m_config); + } + + void set_filename(const std::string &filename) { + m_config.filename = filename; + } + + private: + // Reference to the Environment's configuration + EnvironmentConfiguration &m_config; +}; + +} // namespace hamsterdb + +#endif /* HAM_ENV_TEST_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn.h b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn.h new file mode 100644 index 0000000000..e38e6155dc --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn.h @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The hamsterdb Transaction implementation + * + * hamsterdb stores Transactions in volatile RAM (with an append-only journal + * in case the RAM is lost). Each Transaction and each modification *in* a + * Transaction is stored in a complex data structure. + * + * When a Database is created, it contains a BtreeIndex for persistent + * (committed and flushed) data, and a TransactionIndex for active Transactions + * and those Transactions which were committed but not yet flushed to disk. + * This TransactionTree is implemented as a binary search tree (see rb.h). + * + * Each node in the TransactionTree is implemented by TransactionNode. Each + * node is identified by its database key, and groups all modifications of this + * key (of all Transactions!). + * + * Each modification in the node is implemented by TransactionOperation. There + * is one such TransactionOperation for 'insert', 'erase' etc. The + * TransactionOperations form two linked lists - one stored in the Transaction + * ("all operations from this Transaction") and another one stored in the + * TransactionNode ("all operations on the same key"). + * + * All Transactions in an Environment for a linked list, where the tail is + * the chronologically newest Transaction and the head is the oldest + * (see Transaction::get_newer and Transaction::get_older). + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_TXN_H +#define HAM_TXN_H + +#include "0root/root.h" + +#include <string> + +// Always verify that a file of level N does not include headers > N! +#include "1base/dynamic_array.h" +#include "1base/error.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +// +// A helper structure; ham_txn_t is declared in ham/hamsterdb.h as an +// opaque C structure, but internally we use a C++ class. The ham_txn_t +// struct satisfies the C compiler, and internally we just cast the pointers. +// +struct ham_txn_t +{ + int dummy; +}; + +namespace hamsterdb { + +struct Context; +class Environment; + +// +// An abstract base class for a Transaction. Overwritten for local and +// remote implementations +// +class Transaction +{ + protected: + enum { + // Transaction was aborted + kStateAborted = 0x10000, + + // Transaction was committed + kStateCommitted = 0x20000 + }; + + public: + // Constructor; "begins" the Transaction + // supported flags: HAM_TXN_READ_ONLY, HAM_TXN_TEMPORARY + Transaction(Environment *env, const char *name, uint32_t flags) + : m_id(0), m_env(env), m_flags(flags), m_next(0), m_cursor_refcount(0) { + if (name) + m_name = name; + } + + // Destructor + virtual ~Transaction() { } + + // Commits the Transaction + virtual void commit(uint32_t flags = 0) = 0; + + // Aborts the Transaction + virtual void abort(uint32_t flags = 0) = 0; + + // Returns true if the Transaction was aborted + bool is_aborted() const { + return (m_flags & kStateAborted) != 0; + } + + // Returns true if the Transaction was committed + bool is_committed() const { + return (m_flags & kStateCommitted) != 0; + } + + // Returns the unique id of this Transaction + uint64_t get_id() const { + return (m_id); + } + + // Returns the environment pointer + Environment *get_env() const { + return (m_env); + } + + // Returns the txn name + const std::string &get_name() const { + return (m_name); + } + + // Returns the flags + uint32_t get_flags() const { + return (m_flags); + } + + // Returns the cursor refcount (numbers of Cursors using this Transaction) + uint32_t get_cursor_refcount() const { + return (m_cursor_refcount); + } + + // Increases the cursor refcount (numbers of Cursors using this Transaction) + void increase_cursor_refcount() { + m_cursor_refcount++; + } + + // Decreases the cursor refcount (numbers of Cursors using this Transaction) + void decrease_cursor_refcount() { + ham_assert(m_cursor_refcount > 0); + m_cursor_refcount--; + } + + // Returns the memory buffer for the key data. + // Used to allocate array in ham_find, ham_cursor_move etc. which is + // then returned to the user. + ByteArray &key_arena() { + return (m_key_arena); + } + + // Returns the memory buffer for the record data. + // Used to allocate array in ham_find, ham_cursor_move etc. which is + // then returned to the user. + ByteArray &record_arena() { + return (m_record_arena); + } + + // Returns the next Transaction in the linked list */ + Transaction *get_next() const { + return (m_next); + } + + // Sets the next Transaction in the linked list */ + void set_next(Transaction *n) { + m_next = n; + } + + protected: + // the id of this Transaction + uint64_t m_id; + + // the Environment pointer + Environment *m_env; + + // flags for this Transaction + uint32_t m_flags; + + // the Transaction name + std::string m_name; + + // the linked list of all transactions + Transaction *m_next; + + // reference counter for cursors (number of cursors attached to this txn) + uint32_t m_cursor_refcount; + + // this is where key->data points to when returning a key to the user + ByteArray m_key_arena; + + // this is where record->data points to when returning a record to the user + ByteArray m_record_arena; + + private: + friend class Journal; + + // Sets the unique id of this Transaction; the journal needs this to patch + // in the id when recovering a Transaction + void set_id(uint64_t id) { + m_id = id; + } +}; + + +// +// An abstract base class for the TransactionManager. Overwritten for local and +// remote implementations. +// +// The TransactionManager is part of the Environment and manages all +// Transactions. +// +class TransactionManager +{ + public: + // Constructor + TransactionManager(Environment *env) + : m_env(env), m_oldest_txn(0), m_newest_txn(0) { + } + + // Destructor + virtual ~TransactionManager() { } + + // Begins a new Transaction + virtual void begin(Transaction *txn) = 0; + + // Commits a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t commit(Transaction *txn, uint32_t flags = 0) = 0; + + // Aborts a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t abort(Transaction *txn, uint32_t flags = 0) = 0; + + // Flushes committed (queued) transactions + virtual void flush_committed_txns(Context *context = 0) = 0; + + // Returns the oldest transaction which not yet flushed to disk + Transaction *get_oldest_txn() { + return (m_oldest_txn); + } + + // Returns the newest transaction which not yet flushed to disk + Transaction *get_newest_txn() { + return (m_newest_txn); + } + + protected: + // Adds a new transaction to this Environment + void append_txn_at_tail(Transaction *txn) { + if (!m_newest_txn) { + ham_assert(m_oldest_txn == 0); + m_oldest_txn = txn; + m_newest_txn = txn; + } + else { + m_newest_txn->set_next(txn); + m_newest_txn = txn; + /* if there's no oldest txn (this means: all txn's but the + * current one were already flushed) then set this txn as + * the oldest txn */ + if (!m_oldest_txn) + m_oldest_txn = txn; + } + } + + // Removes a transaction from this Environment + void remove_txn_from_head(Transaction *txn) { + if (m_newest_txn == txn) + m_newest_txn = 0; + + ham_assert(m_oldest_txn == txn); + m_oldest_txn = txn->get_next(); + } + + // The Environment which created this TransactionManager + Environment *m_env; + + // The head of the transaction list (the oldest transaction) + Transaction *m_oldest_txn; + + // The tail of the transaction list (the youngest/newest transaction) + Transaction *m_newest_txn; +}; + +} // namespace hamsterdb + +#endif /* HAM_TXN_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.cc b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.cc new file mode 100644 index 0000000000..b91469239f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.cc @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3btree/btree_cursor.h" +#include "4db/db.h" +#include "4txn/txn.h" +#include "4txn/txn_cursor.h" +#include "4txn/txn_local.h" +#include "4env/env.h" +#include "4cursor/cursor.h" +#include "4context/context.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +void +TransactionCursor::clone(const TransactionCursor *other) +{ + m_coupled_op = 0; + m_coupled_next = 0; + m_coupled_previous = 0; + + if (!other->is_nil()) + couple_to_op(other->get_coupled_op()); +} + +void +TransactionCursor::set_to_nil() +{ + /* uncoupled cursor? remove from the txn_op structure */ + if (!is_nil()) { + TransactionOperation *op = get_coupled_op(); + if (op) + remove_cursor_from_op(op); + m_coupled_op = 0; + } + + /* otherwise cursor is already nil */ +} + +void +TransactionCursor::couple_to_op(TransactionOperation *op) +{ + set_to_nil(); + m_coupled_op = op; + + m_coupled_next = op->cursor_list(); + m_coupled_previous = 0; + + if (op->cursor_list()) { + TransactionCursor *old = op->cursor_list(); + old->m_coupled_previous = this; + } + + op->set_cursor_list(this); +} + +ham_status_t +TransactionCursor::overwrite(Context *context, LocalTransaction *txn, + ham_record_t *record) +{ + ham_assert(context->txn == txn); + + if (is_nil()) + return (HAM_CURSOR_IS_NIL); + + TransactionNode *node = m_coupled_op->get_node(); + + /* an overwrite is actually an insert w/ HAM_OVERWRITE of the + * current key */ + return (((LocalDatabase *)get_db())->insert_txn(context, node->get_key(), + record, HAM_OVERWRITE, this)); +} + +ham_status_t +TransactionCursor::move_top_in_node(TransactionNode *node, + TransactionOperation *op, bool ignore_conflicts, uint32_t flags) +{ + Transaction *optxn = 0; + + if (!op) + op = node->get_newest_op(); + else + goto next; + + while (op) { + optxn = op->get_txn(); + /* only look at ops from the current transaction and from + * committed transactions */ + if (optxn == m_parent->get_txn() || optxn->is_committed()) { + /* a normal (overwriting) insert will return this key */ + if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite)) { + couple_to_op(op); + return (0); + } + /* retrieve a duplicate key */ + if (op->get_flags() & TransactionOperation::kInsertDuplicate) { + /* the duplicates are handled by the caller. here we only + * couple to the first op */ + couple_to_op(op); + return (0); + } + /* a normal erase will return an error (but we still couple the + * cursor because the caller might need to know WHICH key was + * deleted!) */ + if (op->get_flags() & TransactionOperation::kErase) { + couple_to_op(op); + return (HAM_KEY_ERASED_IN_TXN); + } + /* everything else is a bug! */ + ham_assert(op->get_flags() == TransactionOperation::kNop); + } + else if (optxn->is_aborted()) + ; /* nop */ + else if (!ignore_conflicts) { + /* we still have to couple, because higher-level functions + * will need to know about the op when consolidating the trees */ + couple_to_op(op); + return (HAM_TXN_CONFLICT); + } + +next: + m_parent->set_dupecache_index(0); + op = op->get_previous_in_node(); + } + + return (HAM_KEY_NOT_FOUND); +} + +ham_status_t +TransactionCursor::move(uint32_t flags) +{ + ham_status_t st; + TransactionNode *node; + + if (flags & HAM_CURSOR_FIRST) { + /* first set cursor to nil */ + set_to_nil(); + + node = get_db()->txn_index()->get_first(); + if (!node) + return (HAM_KEY_NOT_FOUND); + return (move_top_in_node(node, 0, false, flags)); + } + else if (flags & HAM_CURSOR_LAST) { + /* first set cursor to nil */ + set_to_nil(); + + node = get_db()->txn_index()->get_last(); + if (!node) + return (HAM_KEY_NOT_FOUND); + return (move_top_in_node(node, 0, false, flags)); + } + else if (flags & HAM_CURSOR_NEXT) { + if (is_nil()) + return (HAM_CURSOR_IS_NIL); + + node = m_coupled_op->get_node(); + + ham_assert(!is_nil()); + + /* first move to the next key in the current node; if we fail, + * then move to the next node. repeat till we've found a key or + * till we've reached the end of the tree */ + while (1) { + node = node->get_next_sibling(); + if (!node) + return (HAM_KEY_NOT_FOUND); + st = move_top_in_node(node, 0, true, flags); + if (st == HAM_KEY_NOT_FOUND) + continue; + return (st); + } + } + else if (flags & HAM_CURSOR_PREVIOUS) { + if (is_nil()) + return (HAM_CURSOR_IS_NIL); + + node = m_coupled_op->get_node(); + + ham_assert(!is_nil()); + + /* first move to the previous key in the current node; if we fail, + * then move to the previous node. repeat till we've found a key or + * till we've reached the end of the tree */ + while (1) { + node = node->get_previous_sibling(); + if (!node) + return (HAM_KEY_NOT_FOUND); + st = move_top_in_node(node, 0, true, flags); + if (st == HAM_KEY_NOT_FOUND) + continue; + return (st); + } + } + else { + ham_assert(!"this flag is not yet implemented"); + } + + return (0); +} + +ham_status_t +TransactionCursor::find(ham_key_t *key, uint32_t flags) +{ + TransactionNode *node = 0; + + /* first set cursor to nil */ + set_to_nil(); + + /* then lookup the node */ + if (get_db()->txn_index()) + node = get_db()->txn_index()->get(key, flags); + if (!node) + return (HAM_KEY_NOT_FOUND); + + while (1) { + /* and then move to the newest insert*-op */ + ham_status_t st = move_top_in_node(node, 0, false, 0); + if (st != HAM_KEY_ERASED_IN_TXN) + return (st); + + /* if the key was erased and approx. matching is enabled, then move + * next/prev till we found a valid key. */ + if (flags & HAM_FIND_GT_MATCH) + node = node->get_next_sibling(); + else if (flags & HAM_FIND_LT_MATCH) + node = node->get_previous_sibling(); + else + return (st); + + if (!node) + return (HAM_KEY_NOT_FOUND); + } + + ham_assert(!"should never reach this"); + return (0); +} + +void +TransactionCursor::copy_coupled_key(ham_key_t *key) +{ + Transaction *txn = m_parent->get_txn(); + ham_key_t *source = 0; + + ByteArray *arena = &get_db()->key_arena(txn); + + /* coupled cursor? get key from the txn_op structure */ + if (!is_nil()) { + TransactionNode *node = m_coupled_op->get_node(); + + ham_assert(get_db() == node->get_db()); + source = node->get_key(); + + key->size = source->size; + if (source->data && source->size) { + if (!(key->flags & HAM_KEY_USER_ALLOC)) { + arena->resize(source->size); + key->data = arena->get_ptr(); + } + memcpy(key->data, source->data, source->size); + } + else + key->data = 0; + return; + } + + /* otherwise cursor is nil and we cannot return a key */ + throw Exception(HAM_CURSOR_IS_NIL); +} + +void +TransactionCursor::copy_coupled_record(ham_record_t *record) +{ + ham_record_t *source = 0; + Transaction *txn = m_parent->get_txn(); + + ByteArray *arena = &get_db()->record_arena(txn); + + /* coupled cursor? get record from the txn_op structure */ + if (!is_nil()) { + source = m_coupled_op->get_record(); + + record->size = source->size; + if (source->data && source->size) { + if (!(record->flags & HAM_RECORD_USER_ALLOC)) { + arena->resize(source->size); + record->data = arena->get_ptr(); + } + memcpy(record->data, source->data, source->size); + } + else + record->data = 0; + return; + } + + /* otherwise cursor is nil and we cannot return a key */ + throw Exception(HAM_CURSOR_IS_NIL); +} + +uint64_t +TransactionCursor::get_record_size() +{ + /* coupled cursor? get record from the txn_op structure */ + if (!is_nil()) + return (m_coupled_op->get_record()->size); + + /* otherwise cursor is nil and we cannot return a key */ + throw Exception(HAM_CURSOR_IS_NIL); +} + +LocalDatabase * +TransactionCursor::get_db() +{ + return (m_parent->get_db()); +} + +ham_status_t +TransactionCursor::test_insert(ham_key_t *key, ham_record_t *record, + uint32_t flags) +{ + LocalTransaction *txn = dynamic_cast<LocalTransaction *>(m_parent->get_txn()); + Context context(get_db()->lenv(), txn, get_db()); + + return (get_db()->insert_txn(&context, key, record, flags, this)); +} + +void +TransactionCursor::remove_cursor_from_op(TransactionOperation *op) +{ + ham_assert(!is_nil()); + + if (op->cursor_list() == this) { + op->set_cursor_list(m_coupled_next); + if (m_coupled_next) + m_coupled_next->m_coupled_previous = 0; + } + else { + if (m_coupled_next) + m_coupled_next->m_coupled_previous = m_coupled_previous; + if (m_coupled_previous) + m_coupled_previous->m_coupled_next = m_coupled_next; + } + m_coupled_next = 0; + m_coupled_previous = 0; +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.h b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.h new file mode 100644 index 0000000000..d2f4462f76 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_cursor.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A cursor which can iterate over transaction nodes and operations + * + * A Transaction Cursor can walk over Transaction trees (TransactionIndex). + * + * Transaction Cursors are only used as part of the Cursor structure as defined + * in cursor.h. Like all Transaction operations it is in-memory only, + * traversing the red-black tree that is implemented in txn.h, and + * consolidating multiple operations in a node (i.e. if a Transaction first + * overwrites a record, and another transaction then erases the key). + * + * The Transaction Cursor has two states: either it is coupled to a + * Transaction operation (TransactionOperation) or it is unused. + * + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_TXN_CURSOR_H +#define HAM_TXN_CURSOR_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4txn/txn_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +class Cursor; +struct Context; + +// +// An cursor which can iterate over Transaction nodes +// +class TransactionCursor +{ + public: + // Constructor + TransactionCursor(Cursor *parent) + : m_parent(parent) { + m_coupled_op = 0; + m_coupled_next = 0; + m_coupled_previous = 0; + } + + // Destructor; asserts that the cursor is nil + ~TransactionCursor() { + ham_assert(is_nil()); + } + + // Clones another TransactionCursor + void clone(const TransactionCursor *other); + + // Returns the parent cursor + // TODO this should be private + Cursor *get_parent() { + return (m_parent); + } + + // Couples this cursor to a TransactionOperation structure + void couple_to_op(TransactionOperation *op); + + // Returns the pointer to the coupled TransactionOperation + TransactionOperation *get_coupled_op() const { + return (m_coupled_op); + } + + // Sets the cursor to nil + void set_to_nil(); + + // Returns true if the cursor is nil (does not point to any item) + bool is_nil() const { + return (m_coupled_op == 0); + } + + // Retrieves the key from the current item; creates a deep copy. + // + // If the cursor is uncoupled, HAM_CURSOR_IS_NIL is returned. this + // means that the item was already flushed to the btree, and the caller has + // to use the btree lookup function to retrieve the key. + void copy_coupled_key(ham_key_t *key); + + // Retrieves the record from the current item; creates a deep copy. + // + // If the cursor is uncoupled, HAM_CURSOR_IS_NIL will be returned. this + // means that the item was already flushed to the btree, and the caller has + // to use the btree lookup function to retrieve the record. + void copy_coupled_record(ham_record_t *record); + + // Moves the cursor to first, last, previous or next + ham_status_t move(uint32_t flags); + + // Overwrites the record of a cursor + ham_status_t overwrite(Context *context, LocalTransaction *txn, + ham_record_t *record); + + // Looks up an item, places the cursor + ham_status_t find(ham_key_t *key, uint32_t flags); + + // Retrieves the record size of the current item + uint64_t get_record_size(); + + // Returns the pointer to the next cursor in the linked list of coupled + // cursors + TransactionCursor *get_coupled_next() { + return (m_coupled_next); + } + + // Closes the cursor + void close() { + set_to_nil(); + } + + private: + friend struct TxnCursorFixture; + + // Removes this cursor from this TransactionOperation + void remove_cursor_from_op(TransactionOperation *op); + + // Inserts an item, places the cursor on the new item. + // This function is only used in the unittests. + ham_status_t test_insert(ham_key_t *key, ham_record_t *record, + uint32_t flags); + + // Returns the database pointer + LocalDatabase *get_db(); + + // Moves the cursor to the first valid Operation in a Node + ham_status_t move_top_in_node(TransactionNode *node, + TransactionOperation *op, bool ignore_conflicts, + uint32_t flags); + + // The parent cursor + Cursor *m_parent; + + // A Cursor can either be coupled or nil ("not in list"). If it's + // coupled, it directly points to a TransactionOperation structure. + // If it's nil then |m_coupled_op| is null. + // + // the txn operation to which we're pointing + TransactionOperation *m_coupled_op; + + // a double linked list with other cursors that are coupled + // to the same Operation + TransactionCursor *m_coupled_next, *m_coupled_previous; +}; + +} // namespace hamsterdb + +#endif /* HAM_TXN_CURSOR_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_factory.h b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_factory.h new file mode 100644 index 0000000000..2738f1b4d7 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_factory.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A factory to create TransactionOperation and TransactionNode instances. + * + * @exception_safe: strong + * @thread_safe: yes + */ + +#ifndef HAM_TXN_FACTORY_H +#define HAM_TXN_FACTORY_H + +#include "0root/root.h" + +#include "ham/types.h" + +// Always verify that a file of level N does not include headers > N! +#include "1mem/mem.h" +#include "4txn/txn.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct TransactionFactory +{ + // Creates a new TransactionOperation + static TransactionOperation *create_operation(LocalTransaction *txn, + TransactionNode *node, uint32_t flags, uint32_t orig_flags, + uint64_t lsn, ham_key_t *key, ham_record_t *record) { + TransactionOperation *op; + op = Memory::allocate<TransactionOperation>(sizeof(*op) + + (record ? record->size : 0) + + (key ? key->size : 0)); + op->initialize(txn, node, flags, orig_flags, lsn, key, record); + return (op); + } + + // Destroys a TransactionOperation + static void destroy_operation(TransactionOperation *op) { + op->destroy(); + } +}; + +} // namespace hamsterdb + +#endif /* HAM_TXN_FACTORY_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.cc b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.cc new file mode 100644 index 0000000000..8014b6330f --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.cc @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "3btree/btree_index.h" +#include "3journal/journal.h" +#include "4txn/txn_local.h" +#include "4txn/txn_factory.h" +#include "4txn/txn_cursor.h" +#include "4env/env_local.h" +#include "4cursor/cursor.h" +#include "4context/context.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +/* stuff for rb.h */ +#ifndef __ssize_t_defined +typedef signed ssize_t; +#endif +#ifndef __cplusplus +typedef int bool; +#define true 1 +#define false (!true) +#endif /* __cpluscplus */ + +static int +compare(void *vlhs, void *vrhs) +{ + TransactionNode *lhs = (TransactionNode *)vlhs; + TransactionNode *rhs = (TransactionNode *)vrhs; + LocalDatabase *db = lhs->get_db(); + + if (lhs == rhs) + return (0); + + ham_key_t *lhskey = lhs->get_key(); + ham_key_t *rhskey = rhs->get_key(); + ham_assert(lhskey && rhskey); + return (db->btree_index()->compare_keys(lhskey, rhskey)); +} + +rb_proto(static, rbt_, TransactionIndex, TransactionNode) +rb_gen(static, rbt_, TransactionIndex, TransactionNode, node, compare) + +void +TransactionOperation::initialize(LocalTransaction *txn, TransactionNode *node, + uint32_t flags, uint32_t orig_flags, uint64_t lsn, + ham_key_t *key, ham_record_t *record) +{ + memset(this, 0, sizeof(*this)); + + m_txn = txn; + m_node = node; + m_flags = flags; + m_lsn = lsn; + m_orig_flags = orig_flags; + + /* copy the key data */ + if (key) { + m_key = *key; + if (key->size) { + m_key.data = &m_data[0]; + memcpy(m_key.data, key->data, key->size); + } + } + + /* copy the record data */ + if (record) { + m_record = *record; + if (record->size) { + m_record.data = &m_data[key ? key->size : 0]; + memcpy(m_record.data, record->data, record->size); + } + } +} + +void +TransactionOperation::destroy() +{ + bool delete_node = false; + + /* remove this op from the node */ + TransactionNode *node = get_node(); + if (node->get_oldest_op() == this) { + /* if the node is empty: remove the node from the tree */ + // TODO should this be done in here?? + if (get_next_in_node() == 0) { + node->get_db()->txn_index()->remove(node); + delete_node = true; + } + node->set_oldest_op(get_next_in_node()); + } + + /* remove this operation from the two linked lists */ + TransactionOperation *next = get_next_in_node(); + TransactionOperation *prev = get_previous_in_node(); + if (next) + next->set_previous_in_node(prev); + if (prev) + prev->set_next_in_node(next); + + next = get_next_in_txn(); + prev = get_previous_in_txn(); + if (next) + next->set_previous_in_txn(prev); + if (prev) + prev->set_next_in_txn(next); + + if (delete_node) + delete node; + + Memory::release(this); +} + +TransactionNode * +TransactionNode::get_next_sibling() +{ + return (rbt_next(get_db()->txn_index(), this)); +} + +TransactionNode * +TransactionNode::get_previous_sibling() +{ + return (rbt_prev(get_db()->txn_index(), this)); +} + +TransactionNode::TransactionNode(LocalDatabase *db, ham_key_t *key) + : m_db(db), m_oldest_op(0), m_newest_op(0), m_key(key) +{ + /* make sure that a node with this key does not yet exist */ + // TODO re-enable this; currently leads to a stack overflow because + // TransactionIndex::get() creates a new TransactionNode + // ham_assert(TransactionIndex::get(key, 0) == 0); +} + +TransactionNode::~TransactionNode() +{ +} + +TransactionOperation * +TransactionNode::append(LocalTransaction *txn, uint32_t orig_flags, + uint32_t flags, uint64_t lsn, ham_key_t *key, + ham_record_t *record) +{ + TransactionOperation *op = TransactionFactory::create_operation(txn, + this, flags, orig_flags, lsn, + key, record); + + /* store it in the chronological list which is managed by the node */ + if (!get_newest_op()) { + ham_assert(get_oldest_op() == 0); + set_newest_op(op); + set_oldest_op(op); + } + else { + TransactionOperation *newest = get_newest_op(); + newest->set_next_in_node(op); + op->set_previous_in_node(newest); + set_newest_op(op); + } + + /* store it in the chronological list which is managed by the transaction */ + if (!txn->get_newest_op()) { + ham_assert(txn->get_oldest_op() == 0); + txn->set_newest_op(op); + txn->set_oldest_op(op); + } + else { + TransactionOperation *newest = txn->get_newest_op(); + newest->set_next_in_txn(op); + op->set_previous_in_txn(newest); + txn->set_newest_op(op); + } + + // now that an operation is attached make sure that the node no + // longer uses the temporary key pointer + m_key = 0; + + return (op); +} + +void +TransactionIndex::store(TransactionNode *node) +{ + rbt_insert(this, node); +} + +void +TransactionIndex::remove(TransactionNode *node) +{ +#ifdef HAM_DEBUG + bool found = false; + TransactionNode *n = rbt_first(this); + while (n) { + if (n == node) { + found = true; + break; + } + n = rbt_next(this, n); + } + ham_assert(found == true); +#endif + + rbt_remove(this, node); +} + +LocalTransactionManager::LocalTransactionManager(Environment *env) + : TransactionManager(env), m_txn_id(0), m_queued_txn_for_flush(0), + m_queued_ops_for_flush(0), m_queued_bytes_for_flush(0), + m_txn_threshold(kFlushTxnThreshold), + m_ops_threshold(kFlushOperationsThreshold), + m_bytes_threshold(kFlushBytesThreshold) +{ + if (m_env->get_flags() & HAM_FLUSH_WHEN_COMMITTED) { + m_txn_threshold = 0; + m_ops_threshold = 0; + m_bytes_threshold = 0; + } +} + +LocalTransaction::LocalTransaction(LocalEnvironment *env, const char *name, + uint32_t flags) + : Transaction(env, name, flags), m_log_desc(0), m_oldest_op(0), + m_newest_op(0), m_op_counter(0), m_accum_data_size(0) +{ + LocalTransactionManager *ltm = + (LocalTransactionManager *)env->txn_manager(); + m_id = ltm->get_incremented_txn_id(); + + /* append journal entry */ + if (env->get_flags() & HAM_ENABLE_RECOVERY + && env->get_flags() & HAM_ENABLE_TRANSACTIONS + && !(flags & HAM_TXN_TEMPORARY)) { + env->journal()->append_txn_begin(this, name, + env->next_lsn()); + } +} + +LocalTransaction::~LocalTransaction() +{ + free_operations(); +} + +void +LocalTransaction::commit(uint32_t flags) +{ + /* are cursors attached to this txn? if yes, fail */ + if (get_cursor_refcount()) { + ham_trace(("Transaction cannot be committed till all attached " + "Cursors are closed")); + throw Exception(HAM_CURSOR_STILL_OPEN); + } + + /* this transaction is now committed! */ + m_flags |= kStateCommitted; +} + +void +LocalTransaction::abort(uint32_t flags) +{ + /* are cursors attached to this txn? if yes, fail */ + if (get_cursor_refcount()) { + ham_trace(("Transaction cannot be aborted till all attached " + "Cursors are closed")); + throw Exception(HAM_CURSOR_STILL_OPEN); + } + + /* this transaction is now aborted! */ + m_flags |= kStateAborted; + + /* immediately release memory of the cached operations */ + free_operations(); +} + +void +LocalTransaction::free_operations() +{ + TransactionOperation *n, *op = get_oldest_op(); + + while (op) { + n = op->get_next_in_txn(); + TransactionFactory::destroy_operation(op); + op = n; + } + + set_oldest_op(0); + set_newest_op(0); +} + +TransactionIndex::TransactionIndex(LocalDatabase *db) + : m_db(db) +{ + rbt_new(this); +} + +TransactionIndex::~TransactionIndex() +{ + TransactionNode *node; + + while ((node = rbt_last(this))) { + remove(node); + delete node; + } + + // re-initialize the tree + rbt_new(this); +} + +TransactionNode * +TransactionIndex::get(ham_key_t *key, uint32_t flags) +{ + TransactionNode *node = 0; + int match = 0; + + /* create a temporary node that we can search for */ + TransactionNode tmp(m_db, key); + + /* search if node already exists - if yes, return it */ + if ((flags & HAM_FIND_GEQ_MATCH) == HAM_FIND_GEQ_MATCH) { + node = rbt_nsearch(this, &tmp); + if (node) + match = compare(&tmp, node); + } + else if ((flags & HAM_FIND_LEQ_MATCH) == HAM_FIND_LEQ_MATCH) { + node = rbt_psearch(this, &tmp); + if (node) + match = compare(&tmp, node); + } + else if (flags & HAM_FIND_GT_MATCH) { + node = rbt_search(this, &tmp); + if (node) + node = node->get_next_sibling(); + else + node = rbt_nsearch(this, &tmp); + match = 1; + } + else if (flags & HAM_FIND_LT_MATCH) { + node = rbt_search(this, &tmp); + if (node) + node = node->get_previous_sibling(); + else + node = rbt_psearch(this, &tmp); + match = -1; + } + else + return (rbt_search(this, &tmp)); + + /* tree is empty? */ + if (!node) + return (0); + + /* approx. matching: set the key flag */ + if (match < 0) + ham_key_set_intflags(key, (ham_key_get_intflags(key) + & ~BtreeKey::kApproximate) | BtreeKey::kLower); + else if (match > 0) + ham_key_set_intflags(key, (ham_key_get_intflags(key) + & ~BtreeKey::kApproximate) | BtreeKey::kGreater); + + return (node); +} + +TransactionNode * +TransactionIndex::get_first() +{ + return (rbt_first(this)); +} + +TransactionNode * +TransactionIndex::get_last() +{ + return (rbt_last(this)); +} + +void +TransactionIndex::enumerate(Context *context, + TransactionIndex::Visitor *visitor) +{ + TransactionNode *node = rbt_first(this); + + while (node) { + visitor->visit(context, node); + node = rbt_next(this, node); + } +} + +struct KeyCounter : public TransactionIndex::Visitor +{ + KeyCounter(LocalDatabase *_db, LocalTransaction *_txn, bool _distinct) + : counter(0), distinct(_distinct), txn(_txn), db(_db) { + } + + void visit(Context *context, TransactionNode *node) { + BtreeIndex *be = db->btree_index(); + TransactionOperation *op; + + /* + * look at each tree_node and walk through each operation + * in reverse chronological order (from newest to oldest): + * - is this op part of an aborted txn? then skip it + * - is this op part of a committed txn? then include it + * - is this op part of an txn which is still active? then include it + * - if a committed txn has erased the item then there's no need + * to continue checking older, committed txns of the same key + * + * !! + * if keys are overwritten or a duplicate key is inserted, then + * we have to consolidate the btree keys with the txn-tree keys. + */ + op = node->get_newest_op(); + while (op) { + LocalTransaction *optxn = op->get_txn(); + if (optxn->is_aborted()) + ; // nop + else if (optxn->is_committed() || txn == optxn) { + if (op->get_flags() & TransactionOperation::kIsFlushed) + ; // nop + // if key was erased then it doesn't exist + else if (op->get_flags() & TransactionOperation::kErase) + return; + else if (op->get_flags() & TransactionOperation::kInsert) { + counter++; + return; + } + // key exists - include it + else if ((op->get_flags() & TransactionOperation::kInsert) + || (op->get_flags() & TransactionOperation::kInsertOverwrite)) { + // check if the key already exists in the btree - if yes, + // we do not count it (it will be counted later) + if (HAM_KEY_NOT_FOUND == be->find(context, 0, node->get_key(), 0, 0, 0, 0)) + counter++; + return; + } + else if (op->get_flags() & TransactionOperation::kInsertDuplicate) { + // check if btree has other duplicates + if (0 == be->find(context, 0, node->get_key(), 0, 0, 0, 0)) { + // yes, there's another one + if (distinct) + return; + counter++; + } + else { + // check if other key is in this node + counter++; + if (distinct) + return; + } + } + else if (!(op->get_flags() & TransactionOperation::kNop)) { + ham_assert(!"shouldn't be here"); + return; + } + } + else { // txn is still active + counter++; + } + + op = op->get_previous_in_node(); + } + } + + uint64_t counter; + bool distinct; + LocalTransaction *txn; + LocalDatabase *db; +}; + +uint64_t +TransactionIndex::count(Context *context, LocalTransaction *txn, bool distinct) +{ + KeyCounter k(m_db, txn, distinct); + enumerate(context, &k); + return (k.counter); +} + +void +LocalTransactionManager::begin(Transaction *txn) +{ + append_txn_at_tail(txn); +} + +ham_status_t +LocalTransactionManager::commit(Transaction *htxn, uint32_t flags) +{ + LocalTransaction *txn = dynamic_cast<LocalTransaction *>(htxn); + Context context(lenv(), txn, 0); + + try { + txn->commit(flags); + + /* append journal entry */ + if (m_env->get_flags() & HAM_ENABLE_RECOVERY + && m_env->get_flags() & HAM_ENABLE_TRANSACTIONS + && !(txn->get_flags() & HAM_TXN_TEMPORARY)) + lenv()->journal()->append_txn_commit(txn, + lenv()->next_lsn()); + + /* flush committed transactions */ + m_queued_txn_for_flush++; + m_queued_ops_for_flush += txn->get_op_counter(); + m_queued_bytes_for_flush += txn->get_accum_data_size(); + maybe_flush_committed_txns(&context); + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +ham_status_t +LocalTransactionManager::abort(Transaction *htxn, uint32_t flags) +{ + LocalTransaction *txn = dynamic_cast<LocalTransaction *>(htxn); + Context context(lenv(), txn, 0); + + try { + txn->abort(flags); + + /* append journal entry */ + if (m_env->get_flags() & HAM_ENABLE_RECOVERY + && m_env->get_flags() & HAM_ENABLE_TRANSACTIONS + && !(txn->get_flags() & HAM_TXN_TEMPORARY)) + lenv()->journal()->append_txn_abort(txn, + lenv()->next_lsn()); + + /* flush committed transactions; while this one was not committed, + * we might have cleared the way now to flush other committed + * transactions */ + m_queued_txn_for_flush++; + + /* no need to increment m_queued_{ops,bytes}_for_flush because this + * operation does no longer contain any operations */ + maybe_flush_committed_txns(&context); + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +void +LocalTransactionManager::maybe_flush_committed_txns(Context *context) +{ + if (m_queued_txn_for_flush > m_txn_threshold + || m_queued_ops_for_flush > m_ops_threshold + || m_queued_bytes_for_flush > m_bytes_threshold) + flush_committed_txns_impl(context); +} + +void +LocalTransactionManager::flush_committed_txns(Context *context /* = 0 */) +{ + if (!context) { + Context new_context(lenv(), 0, 0); + flush_committed_txns_impl(&new_context); + } + else + flush_committed_txns_impl(context); +} + +void +LocalTransactionManager::flush_committed_txns_impl(Context *context) +{ + LocalTransaction *oldest; + Journal *journal = lenv()->journal(); + uint64_t highest_lsn = 0; + + ham_assert(context->changeset.is_empty()); + + /* always get the oldest transaction; if it was committed: flush + * it; if it was aborted: discard it; otherwise return */ + while ((oldest = (LocalTransaction *)get_oldest_txn())) { + if (oldest->is_committed()) { + m_queued_ops_for_flush -= oldest->get_op_counter(); + ham_assert(m_queued_ops_for_flush >= 0); + m_queued_bytes_for_flush -= oldest->get_accum_data_size(); + ham_assert(m_queued_bytes_for_flush >= 0); + uint64_t lsn = flush_txn(context, (LocalTransaction *)oldest); + if (lsn > highest_lsn) + highest_lsn = lsn; + + /* this transaction was flushed! */ + if (journal && (oldest->get_flags() & HAM_TXN_TEMPORARY) == 0) + journal->transaction_flushed(oldest); + } + else if (oldest->is_aborted()) { + ; /* nop */ + } + else + break; + + /* it's possible that Transactions were aborted directly, and not through + * the TransactionManager (i.e. in Journal::abort_uncommitted_txns). + * so don't rely on m_queued_txn_for_flush, it might be zero */ + if (m_queued_txn_for_flush > 0) + m_queued_txn_for_flush--; + + /* now remove the txn from the linked list */ + remove_txn_from_head(oldest); + + /* and release the memory */ + delete oldest; + } + + /* now flush the changeset and write the modified pages to disk */ + if (highest_lsn && m_env->get_flags() & HAM_ENABLE_RECOVERY) + context->changeset.flush(highest_lsn); + else + context->changeset.clear(); + + ham_assert(context->changeset.is_empty()); +} + +uint64_t +LocalTransactionManager::flush_txn(Context *context, LocalTransaction *txn) +{ + TransactionOperation *op = txn->get_oldest_op(); + TransactionCursor *cursor = 0; + uint64_t highest_lsn = 0; + + while (op) { + TransactionNode *node = op->get_node(); + + if (op->get_flags() & TransactionOperation::kIsFlushed) + goto next_op; + + // perform the actual operation in the btree + node->get_db()->flush_txn_operation(context, txn, op); + + /* + * this op is about to be flushed! + * + * as a consequence, all (txn)cursors which are coupled to this op + * have to be uncoupled, as their parent (btree) cursor was + * already coupled to the btree item instead + */ + op->set_flushed(); +next_op: + while ((cursor = op->cursor_list())) { + Cursor *pc = cursor->get_parent(); + ham_assert(pc->get_txn_cursor() == cursor); + pc->couple_to_btree(); // TODO merge both calls? + if (!pc->is_nil(Cursor::kTxn)) + pc->set_to_nil(Cursor::kTxn); + } + + ham_assert(op->get_lsn() > highest_lsn); + highest_lsn = op->get_lsn(); + + /* continue with the next operation of this txn */ + op = op->get_next_in_txn(); + } + + return (highest_lsn); +} + +} // namespace hamsterdb diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.h b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.h new file mode 100644 index 0000000000..cfb563466a --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_local.h @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_TXN_LOCAL_H +#define HAM_TXN_LOCAL_H + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "1rb/rb.h" +#include "4txn/txn.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; +class TransactionNode; +class TransactionIndex; +class TransactionCursor; +class LocalTransaction; +class LocalDatabase; +class LocalEnvironment; + + +// +// The TransactionOperation class describes a single operation (i.e. +// insert or erase) in a Transaction. +// +class TransactionOperation +{ + public: + enum { + // a NOP operation (empty) + kNop = 0x000000u, + + // txn operation is an insert + kInsert = 0x010000u, + + // txn operation is an insert w/ overwrite + kInsertOverwrite = 0x020000u, + + // txn operation is an insert w/ duplicate + kInsertDuplicate = 0x040000u, + + // txn operation erases the key + kErase = 0x080000u, + + // txn operation was already flushed + kIsFlushed = 0x100000u + }; + + // Returns the flags + uint32_t get_flags() const { + return (m_flags); + } + + // This Operation was flushed to disk + void set_flushed() { + m_flags |= kIsFlushed; + } + + // Returns the original flags of ham_insert/ham_cursor_insert/ham_erase... + uint32_t get_orig_flags() const { + return (m_orig_flags); + } + + // Returns the referenced duplicate id + uint32_t get_referenced_dupe() const { + return (m_referenced_dupe); + } + + // Sets the referenced duplicate id + void set_referenced_dupe(uint32_t id) { + m_referenced_dupe = id; + } + + // Returns a pointer to the Transaction of this update + LocalTransaction *get_txn() { + return (m_txn); + } + + // Returns a pointer to the parent node of this update */ + TransactionNode *get_node() { + return (m_node); + } + + // Returns the lsn of this operation + uint64_t get_lsn() const { + return (m_lsn); + } + + // Returns the key of this operation + ham_key_t *get_key() { + return (&m_key); + } + + // Returns the record of this operation + ham_record_t *get_record() { + return (&m_record); + } + + // Returns the list of Cursors coupled to this operation + TransactionCursor *cursor_list() { + return (m_cursor_list); + } + + // Sets the list of Cursors coupled to this operation + void set_cursor_list(TransactionCursor *cursors) { + m_cursor_list = cursors; + } + + // Returns the next TransactionOperation which modifies the + // same TransactionNode + TransactionOperation *get_next_in_node() { + return (m_node_next); + } + + // Returns the previous TransactionOperation which modifies the + // same TransactionNode + TransactionOperation *get_previous_in_node() { + return (m_node_prev); + } + + // Returns the next TransactionOperation in the same Transaction + TransactionOperation *get_next_in_txn() { + return (m_txn_next); + } + + // Returns the previous TransactionOperation in the same Transaction + TransactionOperation *get_previous_in_txn() { + return (m_txn_prev); + } + + private: + friend class TransactionNode; + friend struct TransactionFactory; + + // Initialization + void initialize(LocalTransaction *txn, TransactionNode *node, + uint32_t flags, uint32_t orig_flags, uint64_t lsn, + ham_key_t *key, ham_record_t *record); + + // Destructor + void destroy(); + + // Sets the next TransactionOperation which modifies the + // same TransactionNode + void set_next_in_node(TransactionOperation *next) { + m_node_next = next; + } + + // Sets the previous TransactionOperation which modifies the + // same TransactionNode + void set_previous_in_node(TransactionOperation *prev) { + m_node_prev = prev; + } + + // Sets the next TransactionOperation in the same Transaction + void set_next_in_txn(TransactionOperation *next) { + m_txn_next = next; + } + + // Sets the previous TransactionOperation in the same Transaction + void set_previous_in_txn(TransactionOperation *prev) { + m_txn_prev = prev; + } + + // the Transaction of this operation + LocalTransaction *m_txn; + + // the parent node + TransactionNode *m_node; + + // flags and type of this operation; defined in this file + uint32_t m_flags; + + // the original flags of this operation, used when calling + // ham_cursor_insert, ham_insert, ham_erase etc + uint32_t m_orig_flags; + + // the referenced duplicate id (if neccessary) - used if this is + // i.e. a ham_cursor_erase, ham_cursor_overwrite or ham_cursor_insert + // with a DUPLICATE_AFTER/BEFORE flag + // this is 1-based (like dupecache-index, which is also 1-based) + uint32_t m_referenced_dupe; + + // the log serial number (lsn) of this operation + uint64_t m_lsn; + + // a linked list of cursors which are attached to this operation + TransactionCursor *m_cursor_list; + + // next in linked list (managed in TransactionNode) + TransactionOperation *m_node_next; + + // previous in linked list (managed in TransactionNode) + TransactionOperation *m_node_prev; + + // next in linked list (managed in Transaction) + TransactionOperation *m_txn_next; + + // previous in linked list (managed in Transaction) + TransactionOperation *m_txn_prev; + + // the key which is inserted or overwritten + ham_key_t m_key; + + // the record which is inserted or overwritten + ham_record_t m_record; + + // Storage for record->data. This saves us one memory allocation. + uint8_t m_data[1]; +}; + + +// +// A node in the Transaction Index, used as the node structure in rb.h. +// Manages a group of TransactionOperation objects which all modify the +// same key. +// +// To avoid chicken-egg problems when inserting a new TransactionNode +// into the TransactionTree, it is possible to assign a temporary key +// to this node. However, as soon as an operation is attached to this node, +// the TransactionNode class will use the key structure in this operation. +// +// This basically avoids one memory allocation. +// +class TransactionNode +{ + public: + // Constructor; + // The default parameters are required for the compilation of rb.h. + // |key| is just a temporary pointer which allows to create a + // TransactionNode without further memory allocations/copying. The actual + // key is then fetched from |m_oldest_op| as soon as this node is fully + // initialized. + TransactionNode(LocalDatabase *db = 0, ham_key_t *key = 0); + + // Destructor; removes this node from the tree, unless |dont_insert| + // was set to true + ~TransactionNode(); + + // Returns the database + LocalDatabase *get_db() { + return (m_db); + } + + // Returns the modified key + ham_key_t *get_key() { + return (m_oldest_op ? m_oldest_op->get_key() : m_key); + } + + // Retrieves the next larger sibling of a given node, or NULL if there + // is no sibling + TransactionNode *get_next_sibling(); + + // Retrieves the previous larger sibling of a given node, or NULL if there + // is no sibling + TransactionNode *get_previous_sibling(); + + // Returns the first (oldest) TransactionOperation in this node + TransactionOperation *get_oldest_op() { + return (m_oldest_op); + }; + + // Sets the first (oldest) TransactionOperation in this node + void set_oldest_op(TransactionOperation *oldest) { + m_oldest_op = oldest; + } + + // Returns the last (newest) TransactionOperation in this node + TransactionOperation *get_newest_op() { + return (m_newest_op); + }; + + // Sets the last (newest) TransactionOperation in this node + void set_newest_op(TransactionOperation *newest) { + m_newest_op = newest; + } + + // Appends an actual operation to this node + TransactionOperation *append(LocalTransaction *txn, uint32_t orig_flags, + uint32_t flags, uint64_t lsn, ham_key_t *key, + ham_record_t *record); + + // red-black tree stub, required for rb.h + rb_node(TransactionNode) node; + + private: + friend struct TxnFixture; + + // the database - need this to get the compare function + LocalDatabase *m_db; + + // the linked list of operations - head is oldest operation + TransactionOperation *m_oldest_op; + + // the linked list of operations - tail is newest operation + TransactionOperation *m_newest_op; + + // Pointer to the key data; only used as long as there are no operations + // attached. Otherwise we have a chicken-egg problem in rb.h. + ham_key_t *m_key; +}; + + +// +// Each Database has a binary tree which stores the current Transaction +// operations; this tree is implemented in TransactionIndex +// +class TransactionIndex +{ + public: + // Traverses a TransactionIndex; for each node, a callback is executed + struct Visitor { + virtual void visit(Context *context, TransactionNode *node) = 0; + }; + + // Constructor + TransactionIndex(LocalDatabase *db); + + // Destructor; frees all nodes and their operations + ~TransactionIndex(); + + // Stores a new TransactionNode in the index + void store(TransactionNode *node); + + // Removes a TransactionNode from the index + void remove(TransactionNode *node); + + // Visits every node in the TransactionTree + void enumerate(Context *context, Visitor *visitor); + + // Returns an opnode for an optree; if a node with this + // key already exists then the existing node is returned, otherwise NULL. + // |flags| can be HAM_FIND_GEQ_MATCH, HAM_FIND_LEQ_MATCH etc + TransactionNode *get(ham_key_t *key, uint32_t flags); + + // Returns the first (= "smallest") node of the tree, or NULL if the + // tree is empty + TransactionNode *get_first(); + + // Returns the last (= "greatest") node of the tree, or NULL if the + // tree is empty + TransactionNode *get_last(); + + // Returns the key count of this index + uint64_t count(Context *context, LocalTransaction *txn, bool distinct); + + // private: //TODO re-enable this; currently disabled because rb.h needs it + // the Database for all operations in this tree + LocalDatabase *m_db; + + // stuff for rb.h + TransactionNode *rbt_root; + TransactionNode rbt_nil; +}; + + +// +// A local Transaction +// +class LocalTransaction : public Transaction +{ + public: + // Constructor; "begins" the Transaction + // supported flags: HAM_TXN_READ_ONLY, HAM_TXN_TEMPORARY + LocalTransaction(LocalEnvironment *env, const char *name, uint32_t flags); + + // Destructor; frees all TransactionOperation structures associated + // with this Transaction + virtual ~LocalTransaction(); + + // Commits the Transaction + void commit(uint32_t flags = 0); + + // Aborts the Transaction + void abort(uint32_t flags = 0); + + // Returns the first (or 'oldest') TransactionOperation of this Transaction + TransactionOperation *get_oldest_op() const { + return (m_oldest_op); + } + + // Sets the first (or 'oldest') TransactionOperation of this Transaction + void set_oldest_op(TransactionOperation *op) { + m_oldest_op = op; + } + + // Returns the last (or 'newest') TransactionOperation of this Transaction + TransactionOperation *get_newest_op() const { + return (m_newest_op); + } + + // Sets the last (or 'newest') TransactionOperation of this Transaction + void set_newest_op(TransactionOperation *op) { + if (op) { + m_op_counter++; + m_accum_data_size += op->get_record() + ? op->get_record()->size + : 0; + m_accum_data_size += op->get_node()->get_key()->size; + } + m_newest_op = op; + } + + // Returns the number of operations attached to this Transaction + int get_op_counter() const { + return (m_op_counter); + } + + // Returns the accumulated data size of all operations + int get_accum_data_size() const { + return (m_accum_data_size); + } + + private: + friend class Journal; + friend struct TxnFixture; + friend struct TxnCursorFixture; + + // Frees the internal structures; releases all the memory. This is + // called in the destructor, but also when aborting a Transaction + // (before it's deleted by the Environment). + void free_operations(); + + // Returns the index of the journal's log file descriptor + int get_log_desc() const { + return (m_log_desc); + } + + // Sets the index of the journal's log file descriptor + void set_log_desc(int desc) { + m_log_desc = desc; + } + + // index of the log file descriptor for this transaction [0..1] + int m_log_desc; + + // the linked list of operations - head is oldest operation + TransactionOperation *m_oldest_op; + + // the linked list of operations - tail is newest operation + TransactionOperation *m_newest_op; + + // For counting the operations + int m_op_counter; + + // The approximate accumulated memory consumed by this Transaction + // (sums up key->size and record->size over all operations) + int m_accum_data_size; +}; + + +// +// A TransactionManager for local Transactions +// +class LocalTransactionManager : public TransactionManager +{ + enum { + // flush if this limit is exceeded + kFlushTxnThreshold = 64, + + // flush if this limit is exceeded + kFlushOperationsThreshold = kFlushTxnThreshold * 20, + + // flush if this limit is exceeded + kFlushBytesThreshold = 1024 * 1024 // 1 mb - same as journal buffer + }; + + public: + // Constructor + LocalTransactionManager(Environment *env); + + // Begins a new Transaction + virtual void begin(Transaction *txn); + + // Commits a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t commit(Transaction *txn, uint32_t flags = 0); + + // Aborts a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t abort(Transaction *txn, uint32_t flags = 0); + + // Flushes committed (queued) transactions + virtual void flush_committed_txns(Context *context = 0); + + // Increments the global transaction ID and returns the new value. + uint64_t get_incremented_txn_id() { + return (++m_txn_id); + } + + // Returns the current transaction ID; only for testing! + uint64_t test_get_txn_id() const { + return (m_txn_id); + } + + // Sets the current transaction ID; used by the Journal to + // reset the original txn id during recovery. + void set_txn_id(uint64_t id) { + m_txn_id = id; + } + + private: + void flush_committed_txns_impl(Context *context); + + // Flushes a single committed Transaction; returns the lsn of the + // last operation in this transaction + uint64_t flush_txn(Context *context, LocalTransaction *txn); + + // Casts m_env to a LocalEnvironment + LocalEnvironment *lenv() { + return ((LocalEnvironment *)m_env); + } + + // Flushes committed transactions if there are enough committed + // transactions waiting to be flushed, or if other conditions apply + void maybe_flush_committed_txns(Context *context); + + // The current transaction ID + uint64_t m_txn_id; + + // Number of Transactions waiting to be flushed + int m_queued_txn_for_flush; + + // Combined number of Operations in these transactions waiting to be flushed + int m_queued_ops_for_flush; + + // Approx. memory consumption of all these operations in the flush queue + int m_queued_bytes_for_flush; + + // Threshold for transactio queue + int m_txn_threshold; + + // Threshold for transactio queue + int m_ops_threshold; + + // Threshold for transactio queue + int m_bytes_threshold; +}; + +} // namespace hamsterdb + +#endif /* HAM_TXN_LOCAL_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.cc b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.cc new file mode 100644 index 0000000000..2d4403b077 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.cc @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +#include <string.h> + +// Always verify that a file of level N does not include headers > N! +#include "2protobuf/protocol.h" +#include "4txn/txn_remote.h" +#include "4env/env_remote.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +RemoteTransaction::RemoteTransaction(Environment *env, const char *name, + uint32_t flags, uint64_t remote_handle) + : Transaction(env, name, flags), m_remote_handle(remote_handle) +{ +} + +void +RemoteTransaction::commit(uint32_t flags) +{ + /* There's nothing else to do for this Transaction, therefore set it + * to 'aborted' (although it was committed) */ + m_flags |= kStateAborted; +} + +void +RemoteTransaction::abort(uint32_t flags) +{ + /* this transaction is now aborted! */ + m_flags |= kStateAborted; +} + +void +RemoteTransactionManager::begin(Transaction *txn) +{ + append_txn_at_tail(txn); +} + +ham_status_t +RemoteTransactionManager::commit(Transaction *txn, uint32_t flags) +{ + try { + txn->commit(flags); + + /* "flush" (remove) committed and aborted transactions */ + flush_committed_txns(); + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +ham_status_t +RemoteTransactionManager::abort(Transaction *txn, uint32_t flags) +{ + try { + txn->abort(flags); + + /* "flush" (remove) committed and aborted transactions */ + flush_committed_txns(); + } + catch (Exception &ex) { + return (ex.code); + } + return (0); +} + +void +RemoteTransactionManager::flush_committed_txns(Context *context /* = 0 */) +{ + Transaction *oldest; + + while ((oldest = get_oldest_txn())) { + if (oldest->is_committed() || oldest->is_aborted()) { + remove_txn_from_head(oldest); + delete oldest; + } + else + return; + } +} + +} // namespace hamsterdb + +#endif // HAM_ENABLE_REMOTE diff --git a/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.h b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.h new file mode 100644 index 0000000000..4c7d6f46e5 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/4txn/txn_remote.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @exception_safe: unknown + * @thread_safe: unknown + */ + +#ifndef HAM_TXN_REMOTE_H +#define HAM_TXN_REMOTE_H + +#ifdef HAM_ENABLE_REMOTE + +#include "0root/root.h" + +// Always verify that a file of level N does not include headers > N! +#include "4txn/txn.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +namespace hamsterdb { + +struct Context; + +// +// A remote Transaction +// +class RemoteTransaction : public Transaction +{ + public: + // Constructor; "begins" the Transaction + // supported flags: HAM_TXN_READ_ONLY, HAM_TXN_TEMPORARY + RemoteTransaction(Environment *env, const char *name, uint32_t flags, + uint64_t remote_handle); + + // Commits the Transaction + virtual void commit(uint32_t flags = 0); + + // Aborts the Transaction + virtual void abort(uint32_t flags = 0); + + // Returns the remote Transaction handle + uint64_t get_remote_handle() const { + return (m_remote_handle); + } + + private: + // The remote Transaction handle + uint64_t m_remote_handle; +}; + + +// +// A TransactionManager for remote Transactions +// +class RemoteTransactionManager : public TransactionManager +{ + public: + // Constructor + RemoteTransactionManager(Environment *env) + : TransactionManager(env) { + } + + // Begins a new Transaction + virtual void begin(Transaction *txn); + + // Commits a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t commit(Transaction *txn, uint32_t flags = 0); + + // Aborts a Transaction; the derived subclass has to take care of + // flushing and/or releasing memory + virtual ham_status_t abort(Transaction *txn, uint32_t flags = 0); + + // Flushes committed (queued) transactions + virtual void flush_committed_txns(Context *context = 0); +}; + +} // namespace hamsterdb + +#endif // HAM_ENABLE_REMOTE + +#endif /* HAM_TXN_REMOTE_H */ diff --git a/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hamsterdb.cc b/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hamsterdb.cc new file mode 100644 index 0000000000..ed366ed374 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hamsterdb.cc @@ -0,0 +1,1633 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include <stdlib.h> +#include <string.h> + +#include "ham/hamsterdb.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "1base/dynamic_array.h" +#include "1mem/mem.h" +#include "2config/db_config.h" +#include "2config/env_config.h" +#include "2page/page.h" +#ifdef HAM_ENABLE_REMOTE +# include "2protobuf/protocol.h" +#endif +#include "2device/device.h" +#include "3btree/btree_stats.h" +#include "3blob_manager/blob_manager.h" +#include "3btree/btree_index.h" +#include "3btree/btree_cursor.h" +#include "4cursor/cursor.h" +#include "4db/db.h" +#include "4env/env.h" +#include "4env/env_header.h" +#include "4env/env_local.h" +#include "4env/env_remote.h" +#include "4txn/txn.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +/* return true if the filename is for a local file */ +static bool +filename_is_local(const char *filename) +{ + return (!filename || strstr(filename, "ham://") != filename); +} + +ham_status_t +ham_txn_begin(ham_txn_t **htxn, ham_env_t *henv, const char *name, + void *, uint32_t flags) +{ + Transaction **ptxn = (Transaction **)htxn; + + if (!ptxn) { + ham_trace(("parameter 'txn' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + *ptxn = 0; + + if (!henv) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Environment *env = (Environment *)henv; + + return (env->txn_begin(ptxn, name, flags)); +} + +HAM_EXPORT const char * +ham_txn_get_name(ham_txn_t *htxn) +{ + Transaction *txn = (Transaction *)htxn; + if (!txn) + return (0); + + const std::string &name = txn->get_env()->txn_get_name(txn); + return (name.empty() ? 0 : name.c_str()); +} + +ham_status_t +ham_txn_commit(ham_txn_t *htxn, uint32_t flags) +{ + Transaction *txn = (Transaction *)htxn; + if (!txn) { + ham_trace(("parameter 'txn' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Environment *env = txn->get_env(); + + return (env->txn_commit(txn, flags)); +} + +ham_status_t +ham_txn_abort(ham_txn_t *htxn, uint32_t flags) +{ + Transaction *txn = (Transaction *)htxn; + if (!txn) { + ham_trace(("parameter 'txn' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Environment *env = txn->get_env(); + + return (env->txn_abort(txn, flags)); +} + +const char * HAM_CALLCONV +ham_strerror(ham_status_t result) +{ + switch (result) { + case HAM_SUCCESS: + return ("Success"); + case HAM_INV_KEY_SIZE: + return ("Invalid key size"); + case HAM_INV_RECORD_SIZE: + return ("Invalid record size"); + case HAM_INV_PAGESIZE: + return ("Invalid page size"); + case HAM_OUT_OF_MEMORY: + return ("Out of memory"); + case HAM_INV_PARAMETER: + return ("Invalid parameter"); + case HAM_INV_FILE_HEADER: + return ("Invalid database file header"); + case HAM_INV_FILE_VERSION: + return ("Invalid database file version"); + case HAM_KEY_NOT_FOUND: + return ("Key not found"); + case HAM_DUPLICATE_KEY: + return ("Duplicate key"); + case HAM_INTEGRITY_VIOLATED: + return ("Internal integrity violated"); + case HAM_INTERNAL_ERROR: + return ("Internal error"); + case HAM_WRITE_PROTECTED: + return ("Database opened in read-only mode"); + case HAM_BLOB_NOT_FOUND: + return ("Data blob not found"); + case HAM_IO_ERROR: + return ("System I/O error"); + case HAM_NOT_IMPLEMENTED: + return ("Operation not implemented"); + case HAM_FILE_NOT_FOUND: + return ("File not found"); + case HAM_WOULD_BLOCK: + return ("Operation would block"); + case HAM_NOT_READY: + return ("Object was not initialized correctly"); + case HAM_CURSOR_STILL_OPEN: + return ("Cursor must be closed prior to Transaction abort/commit"); + case HAM_FILTER_NOT_FOUND: + return ("Record filter or file filter not found"); + case HAM_TXN_CONFLICT: + return ("Operation conflicts with another Transaction"); + case HAM_TXN_STILL_OPEN: + return ("Database cannot be closed because it is modified in a " + "Transaction"); + case HAM_CURSOR_IS_NIL: + return ("Cursor points to NIL"); + case HAM_DATABASE_NOT_FOUND: + return ("Database not found"); + case HAM_DATABASE_ALREADY_EXISTS: + return ("Database name already exists"); + case HAM_DATABASE_ALREADY_OPEN: + return ("Database already open, or: Database handle " + "already initialized"); + case HAM_ENVIRONMENT_ALREADY_OPEN: + return ("Environment already open, or: Environment handle " + "already initialized"); + case HAM_LIMITS_REACHED: + return ("Database limits reached"); + case HAM_ALREADY_INITIALIZED: + return ("Object was already initialized"); + case HAM_NEED_RECOVERY: + return ("Database needs recovery"); + case HAM_LOG_INV_FILE_HEADER: + return ("Invalid log file header"); + case HAM_NETWORK_ERROR: + return ("Remote I/O error/Network error"); + default: + return ("Unknown error"); + } +} + +/** + * Prepares a @ref ham_key_t structure for returning key data in. + * + * This function checks whether the @ref ham_key_t structure has been + * properly initialized by the user and resets all internal used elements. + * + * @return true when the @a key structure has been initialized correctly + * before. + * + * @return false when the @a key structure has @e not been initialized + * correctly before. + */ +static inline bool +__prepare_key(ham_key_t *key) +{ + if (unlikely(key->size && !key->data)) { + ham_trace(("key->size != 0, but key->data is NULL")); + return (false); + } + if (unlikely(key->flags != 0 && key->flags != HAM_KEY_USER_ALLOC)) { + ham_trace(("invalid flag in key->flags")); + return (false); + } + key->_flags = 0; + return (true); +} + +/** + * Prepares a @ref ham_record_t structure for returning record data in. + * + * This function checks whether the @ref ham_record_t structure has been + * properly initialized by the user and resets all internal used elements. + * + * @return true when the @a record structure has been initialized + * correctly before. + * + * @return false when the @a record structure has @e not been + * initialized correctly before. + */ +static inline bool +__prepare_record(ham_record_t *record) +{ + if (unlikely(record->size && !record->data)) { + ham_trace(("record->size != 0, but record->data is NULL")); + return false; + } + if (unlikely(record->flags & HAM_DIRECT_ACCESS)) + record->flags &= ~HAM_DIRECT_ACCESS; + if (unlikely(record->flags != 0 && record->flags != HAM_RECORD_USER_ALLOC)) { + ham_trace(("invalid flag in record->flags")); + return (false); + } + return (true); +} + +void HAM_CALLCONV +ham_get_version(uint32_t *major, uint32_t *minor, uint32_t *revision) +{ + if (major) + *major = HAM_VERSION_MAJ; + if (minor) + *minor = HAM_VERSION_MIN; + if (revision) + *revision = HAM_VERSION_REV; +} + +ham_status_t HAM_CALLCONV +ham_env_create(ham_env_t **henv, const char *filename, + uint32_t flags, uint32_t mode, const ham_parameter_t *param) +{ + EnvironmentConfiguration config; + config.filename = filename ? filename : ""; + config.file_mode = mode; + + if (!henv) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + *henv = 0; + + /* creating a file in READ_ONLY mode? doesn't make sense */ + if (flags & HAM_READ_ONLY) { + ham_trace(("cannot create a file in read-only mode")); + return (HAM_INV_PARAMETER); + } + + /* in-memory? recovery is not possible */ + if ((flags & HAM_IN_MEMORY) && (flags & HAM_ENABLE_RECOVERY)) { + ham_trace(("combination of HAM_IN_MEMORY and HAM_ENABLE_RECOVERY " + "not allowed")); + return (HAM_INV_PARAMETER); + } + + if (flags & HAM_ENABLE_CRC32) { + ham_trace(("Crc32 is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + } + + /* HAM_ENABLE_TRANSACTIONS implies HAM_ENABLE_RECOVERY, unless explicitly + * disabled */ + if ((flags & HAM_ENABLE_TRANSACTIONS) && !(flags & HAM_DISABLE_RECOVERY)) + flags |= HAM_ENABLE_RECOVERY; + + /* flag HAM_AUTO_RECOVERY implies HAM_ENABLE_RECOVERY */ + if (flags & HAM_AUTO_RECOVERY) + flags |= HAM_ENABLE_RECOVERY; + + /* in-memory with Transactions? disable recovery */ + if (flags & HAM_IN_MEMORY) + flags &= ~HAM_ENABLE_RECOVERY; + + if (param) { + for (; param->name; param++) { + switch (param->name) { + case HAM_PARAM_JOURNAL_COMPRESSION: + ham_trace(("Journal compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_CACHE_SIZE: + if (flags & HAM_IN_MEMORY && param->value != 0) { + ham_trace(("combination of HAM_IN_MEMORY and cache size != 0 " + "not allowed")); + return (HAM_INV_PARAMETER); + } + /* don't allow cache limits with unlimited cache */ + if (flags & HAM_CACHE_UNLIMITED && param->value != 0) { + ham_trace(("combination of HAM_CACHE_UNLIMITED and cache size != 0 " + "not allowed")); + return (HAM_INV_PARAMETER); + } + if (param->value > 0) + config.cache_size_bytes = (size_t)param->value; + break; + case HAM_PARAM_PAGE_SIZE: + if (param->value != 1024 && param->value % 2048 != 0) { + ham_trace(("invalid page size - must be 1024 or a multiple of 2048")); + return (HAM_INV_PAGESIZE); + } + if (param->value > 0) + config.page_size_bytes = (uint32_t)param->value; + break; + case HAM_PARAM_FILE_SIZE_LIMIT: + if (param->value > 0) + config.file_size_limit_bytes = (size_t)param->value; + break; + case HAM_PARAM_JOURNAL_SWITCH_THRESHOLD: + config.journal_switch_threshold = (uint32_t)param->value; + break; + case HAM_PARAM_LOG_DIRECTORY: + config.log_filename = (const char *)param->value; + break; + case HAM_PARAM_NETWORK_TIMEOUT_SEC: + config.remote_timeout_sec = (uint32_t)param->value; + break; + case HAM_PARAM_ENCRYPTION_KEY: + ham_trace(("Encryption is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_POSIX_FADVISE: + config.posix_advice = (int)param->value; + break; + default: + ham_trace(("unknown parameter %d", (int)param->name)); + return (HAM_INV_PARAMETER); + } + } + } + + if (config.filename.empty() && !(flags & HAM_IN_MEMORY)) { + ham_trace(("filename is missing")); + return (HAM_INV_PARAMETER); + } + + config.flags = flags; + + /* + * make sure that max_databases actually fit in a header + * page! + * leave at least 128 bytes for other header data + */ + config.max_databases = config.page_size_bytes + - sizeof(PEnvironmentHeader) - 128; + config.max_databases /= sizeof(PBtreeHeader); + + ham_status_t st = 0; + Environment *env = 0; + + if (filename_is_local(config.filename.c_str())) { + env = new LocalEnvironment(config); + } + else { +#ifndef HAM_ENABLE_REMOTE + return (HAM_NOT_IMPLEMENTED); +#else // HAM_ENABLE_REMOTE + env = new RemoteEnvironment(config); +#endif + } + +#ifdef HAM_ENABLE_REMOTE + atexit(Protocol::shutdown); +#endif + + /* and finish the initialization of the Environment */ + st = env->create(); + + /* flush the environment to make sure that the header page is written + * to disk TODO required?? */ + if (st == 0) + st = env->flush(0); + + if (st) { + env->close(HAM_AUTO_CLEANUP); + delete env; + return (st); + } + + *henv = (ham_env_t *)env; + return (0); +} + +ham_status_t HAM_CALLCONV +ham_env_create_db(ham_env_t *henv, ham_db_t **hdb, uint16_t db_name, + uint32_t flags, const ham_parameter_t *param) +{ + Environment *env = (Environment *)henv; + DatabaseConfiguration config; + + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + *hdb = 0; + + if (!db_name || (db_name >= 0xf000)) { + ham_trace(("invalid database name")); + return (HAM_INV_PARAMETER); + } + + config.db_name = db_name; + config.flags = flags; + + return (env->create_db((Database **)hdb, config, param)); +} + +ham_status_t HAM_CALLCONV +ham_env_open_db(ham_env_t *henv, ham_db_t **hdb, uint16_t db_name, + uint32_t flags, const ham_parameter_t *param) +{ + Environment *env = (Environment *)henv; + DatabaseConfiguration config; + + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + *hdb = 0; + + if (!db_name) { + ham_trace(("parameter 'db_name' must not be 0")); + return (HAM_INV_PARAMETER); + } + if (db_name >= 0xf000) { + ham_trace(("database name must be lower than 0xf000")); + return (HAM_INV_PARAMETER); + } + if (env->get_flags() & HAM_IN_MEMORY) { + ham_trace(("cannot open a Database in an In-Memory Environment")); + return (HAM_INV_PARAMETER); + } + + config.flags = flags; + config.db_name = db_name; + + return (env->open_db((Database **)hdb, config, param)); +} + +ham_status_t HAM_CALLCONV +ham_env_open(ham_env_t **henv, const char *filename, uint32_t flags, + const ham_parameter_t *param) +{ + EnvironmentConfiguration config; + config.filename = filename ? filename : ""; + + if (!henv) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + *henv = 0; + + /* cannot open an in-memory-db */ + if (flags & HAM_IN_MEMORY) { + ham_trace(("cannot open an in-memory database")); + return (HAM_INV_PARAMETER); + } + + /* HAM_ENABLE_DUPLICATE_KEYS has to be specified in ham_env_create_db, + * not ham_env_open */ + if (flags & HAM_ENABLE_DUPLICATE_KEYS) { + ham_trace(("invalid flag HAM_ENABLE_DUPLICATE_KEYS (only allowed when " + "creating a database")); + return (HAM_INV_PARAMETER); + } + + if (flags & HAM_ENABLE_CRC32) { + ham_trace(("Crc32 is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + } + + /* HAM_ENABLE_TRANSACTIONS implies HAM_ENABLE_RECOVERY, unless explicitly + * disabled */ + if ((flags & HAM_ENABLE_TRANSACTIONS) && !(flags & HAM_DISABLE_RECOVERY)) + flags |= HAM_ENABLE_RECOVERY; + + /* flag HAM_AUTO_RECOVERY implies HAM_ENABLE_RECOVERY */ + if (flags & HAM_AUTO_RECOVERY) + flags |= HAM_ENABLE_RECOVERY; + + if (config.filename.empty() && !(flags & HAM_IN_MEMORY)) { + ham_trace(("filename is missing")); + return (HAM_INV_PARAMETER); + } + + if (param) { + for (; param->name; param++) { + switch (param->name) { + case HAM_PARAM_JOURNAL_COMPRESSION: + ham_trace(("Journal compression is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_CACHE_SIZE: + /* don't allow cache limits with unlimited cache */ + if (flags & HAM_CACHE_UNLIMITED && param->value != 0) { + ham_trace(("combination of HAM_CACHE_UNLIMITED and cache size != 0 " + "not allowed")); + return (HAM_INV_PARAMETER); + } + if (param->value > 0) + config.cache_size_bytes = param->value; + break; + case HAM_PARAM_FILE_SIZE_LIMIT: + if (param->value > 0) + config.file_size_limit_bytes = (size_t)param->value; + break; + case HAM_PARAM_JOURNAL_SWITCH_THRESHOLD: + config.journal_switch_threshold = (uint32_t)param->value; + break; + case HAM_PARAM_LOG_DIRECTORY: + config.log_filename = (const char *)param->value; + break; + case HAM_PARAM_NETWORK_TIMEOUT_SEC: + config.remote_timeout_sec = (uint32_t)param->value; + break; + case HAM_PARAM_ENCRYPTION_KEY: + ham_trace(("Encryption is only available in hamsterdb pro")); + return (HAM_NOT_IMPLEMENTED); + case HAM_PARAM_POSIX_FADVISE: + config.posix_advice = (int)param->value; + break; + default: + ham_trace(("unknown parameter %d", (int)param->name)); + return (HAM_INV_PARAMETER); + } + } + } + + config.flags = flags; + + ham_status_t st = 0; + Environment *env = 0; + + if (filename_is_local(config.filename.c_str())) { + env = new LocalEnvironment(config); + } + else { +#ifndef HAM_ENABLE_REMOTE + return (HAM_NOT_IMPLEMENTED); +#else // HAM_ENABLE_REMOTE + env = new RemoteEnvironment(config); +#endif + } + +#ifdef HAM_ENABLE_REMOTE + atexit(Protocol::shutdown); +#endif + + /* and finish the initialization of the Environment */ + st = env->open(); + + if (st) { + (void)env->close(HAM_AUTO_CLEANUP); + delete env; + return (st); + } + + *henv = (ham_env_t *)env; + return (0); +} + +ham_status_t HAM_CALLCONV +ham_env_rename_db(ham_env_t *henv, uint16_t oldname, uint16_t newname, + uint32_t flags) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (!oldname) { + ham_trace(("parameter 'oldname' must not be 0")); + return (HAM_INV_PARAMETER); + } + if (!newname) { + ham_trace(("parameter 'newname' must not be 0")); + return (HAM_INV_PARAMETER); + } + if (newname >= 0xf000) { + ham_trace(("parameter 'newname' must be lower than 0xf000")); + return (HAM_INV_PARAMETER); + } + + /* no need to do anything if oldname==newname */ + if (oldname == newname) + return (0); + + /* rename the database */ + return (env->rename_db(oldname, newname, flags)); +} + +ham_status_t HAM_CALLCONV +ham_env_erase_db(ham_env_t *henv, uint16_t name, uint32_t flags) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (!name) { + ham_trace(("parameter 'name' must not be 0")); + return (HAM_INV_PARAMETER); + } + + /* erase the database */ + return (env->erase_db(name, flags)); +} + +ham_status_t HAM_CALLCONV +ham_env_get_database_names(ham_env_t *henv, uint16_t *names, uint32_t *count) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (!names) { + ham_trace(("parameter 'names' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!count) { + ham_trace(("parameter 'count' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + /* get all database names */ + return (env->get_database_names(names, count)); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_env_get_parameters(ham_env_t *henv, ham_parameter_t *param) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (!param) { + ham_trace(("parameter 'param' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + /* get the parameters */ + return (env->get_parameters(param)); +} + +ham_status_t HAM_CALLCONV +ham_env_flush(ham_env_t *henv, uint32_t flags) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (flags && flags != HAM_FLUSH_COMMITTED_TRANSACTIONS) { + ham_trace(("parameter 'flags' is unused, set to 0")); + return (HAM_INV_PARAMETER); + } + + /* flush the Environment */ + return (env->flush(flags)); +} + +ham_status_t HAM_CALLCONV +ham_env_close(ham_env_t *henv, uint32_t flags) +{ + ham_status_t st; + Environment *env = (Environment *)henv; + + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + try { + /* close the environment */ + st = env->close(flags); + if (st) + return (st); + + delete env; + return (0); + } + catch (Exception &ex) { + return (ex.code); + } +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_get_parameters(ham_db_t *hdb, ham_parameter_t *param) +{ + Database *db = (Database *)hdb; + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (!param) { + ham_trace(("parameter 'param' must not be NULL")); + return HAM_INV_PARAMETER; + } + + ScopedLock lock(db->get_env()->mutex()); + + /* get the parameters */ + return (db->set_error(db->get_parameters(param))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_get_error(ham_db_t *hdb) +{ + Database *db = (Database *)hdb; + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (0); + } + + ScopedLock lock; + if (db->get_env()) + lock = ScopedLock(db->get_env()->mutex()); + + return (db->get_error()); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_set_compare_func(ham_db_t *hdb, ham_compare_func_t foo) +{ + Database *db = (Database *)hdb; + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!foo) { + ham_trace(("function pointer must not be NULL")); + return (HAM_INV_PARAMETER); + } + + LocalDatabase *ldb = dynamic_cast<LocalDatabase *>(db); + if (!ldb) { + ham_trace(("operation not possible for remote databases")); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(ldb->get_env()->mutex()); + + /* set the compare functions */ + return (ldb->set_error(ldb->set_compare_func(foo))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_find(ham_db_t *hdb, ham_txn_t *htxn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + Environment *env; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + env = db->get_env(); + + ScopedLock lock(env->mutex()); + + if (!key) { + ham_trace(("parameter 'key' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!record) { + ham_trace(("parameter 'record' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_PREPEND) { + ham_trace(("flag HAM_HINT_PREPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_APPEND) { + ham_trace(("flag HAM_HINT_APPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DIRECT_ACCESS) + && !(env->get_flags() & HAM_IN_MEMORY)) { + ham_trace(("flag HAM_DIRECT_ACCESS is only allowed in " + "In-Memory Databases")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DIRECT_ACCESS) + && (env->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_DIRECT_ACCESS is not allowed in " + "combination with Transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (db->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_PARTIAL is not allowed in combination with " + "transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + /* record number: make sure that we have a valid key structure */ + if ((db->get_flags() & HAM_RECORD_NUMBER32) && !key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((db->get_flags() & HAM_RECORD_NUMBER64) && !key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + if (!__prepare_key(key) || !__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + + return (db->set_error(db->find(0, txn, key, record, flags))); +} + +HAM_EXPORT int HAM_CALLCONV +ham_key_get_approximate_match_type(ham_key_t *key) +{ + if (key && (ham_key_get_intflags(key) & BtreeKey::kApproximate)) { + int rv = (ham_key_get_intflags(key) & BtreeKey::kLower) ? -1 : +1; + return (rv); + } + + return (0); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_insert(ham_db_t *hdb, ham_txn_t *htxn, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + Environment *env; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return HAM_INV_PARAMETER; + } + env = db->get_env(); + + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(env->mutex()); + + if (!key) { + ham_trace(("parameter 'key' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!record) { + ham_trace(("parameter 'record' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_APPEND) { + ham_trace(("flags HAM_HINT_APPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_PREPEND) { + ham_trace(("flags HAM_HINT_PREPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (db->get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot insert in a read-only database")); + return (db->set_error(HAM_WRITE_PROTECTED)); + } + if ((flags & HAM_OVERWRITE) && (flags & HAM_DUPLICATE)) { + ham_trace(("cannot combine HAM_OVERWRITE and HAM_DUPLICATE")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (db->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_PARTIAL is not allowed in combination with " + "transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) && (record->size <= sizeof(uint64_t))) { + ham_trace(("flag HAM_PARTIAL is not allowed if record->size " + "<= 8")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (record->partial_size + record->partial_offset > record->size)) { + ham_trace(("partial offset+size is greater than the total " + "record size")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DUPLICATE) + && !(db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS)) { + ham_trace(("database does not support duplicate keys " + "(see HAM_ENABLE_DUPLICATE_KEYS)")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DUPLICATE_INSERT_AFTER) + || (flags & HAM_DUPLICATE_INSERT_BEFORE) + || (flags & HAM_DUPLICATE_INSERT_LAST) + || (flags & HAM_DUPLICATE_INSERT_FIRST)) { + ham_trace(("function does not support flags HAM_DUPLICATE_INSERT_*; " + "see ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + if (!__prepare_key(key) || !__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + + /* allocate temp. storage for a recno key */ + if ((db->get_flags() & HAM_RECORD_NUMBER32) + || (db->get_flags() & HAM_RECORD_NUMBER64)) { + if (flags & HAM_OVERWRITE) { + if (!key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + else { + if (key->flags & HAM_KEY_USER_ALLOC) { + if (!key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + else { + if (key->data || key->size) { + ham_trace(("key->size must be 0, key->data must be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + } + } + + return (db->set_error(db->insert(0, txn, key, record, flags))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_erase(ham_db_t *hdb, ham_txn_t *htxn, ham_key_t *key, uint32_t flags) +{ + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + Environment *env; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + env = db->get_env(); + + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(env->mutex()); + + if (!key) { + ham_trace(("parameter 'key' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_PREPEND) { + ham_trace(("flag HAM_HINT_PREPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_APPEND) { + ham_trace(("flag HAM_HINT_APPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (db->get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot erase from a read-only database")); + return (HAM_WRITE_PROTECTED); + } + + if (!__prepare_key(key)) + return (db->set_error(HAM_INV_PARAMETER)); + + return (db->set_error(db->erase(0, txn, key, flags))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_check_integrity(ham_db_t *hdb, uint32_t flags) +{ + Database *db = (Database *)hdb; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if (flags && flags != HAM_PRINT_GRAPH) { + ham_trace(("unknown flag 0x%u", flags)); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + + return (db->set_error(db->check_integrity(flags))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_db_close(ham_db_t *hdb, uint32_t flags) +{ + Database *db = (Database *)hdb; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + if ((flags & HAM_TXN_AUTO_ABORT) && (flags & HAM_TXN_AUTO_COMMIT)) { + ham_trace(("invalid combination of flags: HAM_TXN_AUTO_ABORT + " + "HAM_TXN_AUTO_COMMIT")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + Environment *env = db->get_env(); + + /* it's ok to close an uninitialized Database */ + if (!env) { + delete db; + return (0); + } + + return (env->close_db(db, flags)); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_create(ham_cursor_t **hcursor, ham_db_t *hdb, ham_txn_t *htxn, + uint32_t flags) +{ + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + Environment *env; + Cursor **cursor = 0; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + cursor = (Cursor **)hcursor; + env = db->get_env(); + + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(env->mutex()); + + return (db->set_error(db->cursor_create(cursor, txn, flags))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_clone(ham_cursor_t *hsrc, ham_cursor_t **hdest) +{ + Database *db; + + if (!hsrc) { + ham_trace(("parameter 'src' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!hdest) { + ham_trace(("parameter 'dest' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *src, **dest; + src = (Cursor *)hsrc; + dest = (Cursor **)hdest; + + db = src->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + return (db->set_error(db->cursor_clone(dest, src))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_overwrite(ham_cursor_t *hcursor, ham_record_t *record, + uint32_t flags) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (flags) { + ham_trace(("function does not support a non-zero flags value; " + "see ham_cursor_insert for an alternative then")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!record) { + ham_trace(("parameter 'record' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + if (db->get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot overwrite in a read-only database")); + return (db->set_error(HAM_WRITE_PROTECTED)); + } + + return (db->set_error(db->cursor_overwrite(cursor, record, flags))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_move(ham_cursor_t *hcursor, ham_key_t *key, + ham_record_t *record, uint32_t flags) +{ + Database *db; + Environment *env; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if ((flags & HAM_ONLY_DUPLICATES) && (flags & HAM_SKIP_DUPLICATES)) { + ham_trace(("combination of HAM_ONLY_DUPLICATES and " + "HAM_SKIP_DUPLICATES not allowed")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + env = db->get_env(); + + if ((flags & HAM_DIRECT_ACCESS) + && !(env->get_flags() & HAM_IN_MEMORY)) { + ham_trace(("flag HAM_DIRECT_ACCESS is only allowed in " + "In-Memory Databases")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DIRECT_ACCESS) + && (env->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_DIRECT_ACCESS is not allowed in " + "combination with Transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (db->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_PARTIAL is not allowed in combination with " + "transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + if (key && !__prepare_key(key)) + return (db->set_error(HAM_INV_PARAMETER)); + if (record && !__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + + return (db->set_error(db->cursor_move(cursor, key, record, flags))); +} + +HAM_EXPORT ham_status_t HAM_CALLCONV +ham_cursor_find(ham_cursor_t *hcursor, ham_key_t *key, ham_record_t *record, + uint32_t flags) +{ + Database *db; + Environment *env; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + env = db->get_env(); + + ScopedLock lock; + if (!(flags & HAM_DONT_LOCK)) + lock = ScopedLock(env->mutex()); + + if (!key) { + ham_trace(("parameter 'key' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DIRECT_ACCESS) + && !(env->get_flags() & HAM_IN_MEMORY)) { + ham_trace(("flag HAM_DIRECT_ACCESS is only allowed in " + "In-Memory Databases")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DIRECT_ACCESS) + && (env->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_DIRECT_ACCESS is not allowed in " + "combination with Transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_PREPEND) { + ham_trace(("flag HAM_HINT_PREPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_APPEND) { + ham_trace(("flag HAM_HINT_APPEND is only allowed in " + "ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (db->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_PARTIAL is not allowed in combination with " + "transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + if (key && !__prepare_key(key)) + return (db->set_error(HAM_INV_PARAMETER)); + if (record && !__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + + return (db->set_error(db->find(cursor, cursor->get_txn(), + key, record, flags))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_insert(ham_cursor_t *hcursor, ham_key_t *key, ham_record_t *record, + uint32_t flags) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (!key) { + ham_trace(("parameter 'key' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!record) { + ham_trace(("parameter 'record' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags&HAM_HINT_APPEND) && (flags&HAM_HINT_PREPEND)) { + ham_trace(("flags HAM_HINT_APPEND and HAM_HINT_PREPEND " + "are mutually exclusive")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (!__prepare_key(key) || !__prepare_record(record)) + return (db->set_error(HAM_INV_PARAMETER)); + + if (db->get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot insert to a read-only database")); + return (db->set_error(HAM_WRITE_PROTECTED)); + } + if ((flags & HAM_DUPLICATE) && (flags & HAM_OVERWRITE)) { + ham_trace(("cannot combine HAM_DUPLICATE and HAM_OVERWRITE")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_DUPLICATE) + && !(db->get_flags() & HAM_ENABLE_DUPLICATE_KEYS)) { + ham_trace(("database does not support duplicate keys " + "(see HAM_ENABLE_DUPLICATE_KEYS)")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) + && (db->get_flags() & HAM_ENABLE_TRANSACTIONS)) { + ham_trace(("flag HAM_PARTIAL is not allowed in combination with " + "transactions")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags&HAM_PARTIAL) + && (record->partial_size + record->partial_offset > record->size)) { + ham_trace(("partial offset+size is greater than the total " + "record size")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if ((flags & HAM_PARTIAL) && (record->size <= sizeof(uint64_t))) { + ham_trace(("flag HAM_PARTIAL is not allowed if record->size <= 8")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + /* + * set flag HAM_DUPLICATE if one of DUPLICATE_INSERT* is set, but do + * not allow these flags if duplicate sorting is enabled + */ + if (flags & (HAM_DUPLICATE_INSERT_AFTER + | HAM_DUPLICATE_INSERT_BEFORE + | HAM_DUPLICATE_INSERT_LAST + | HAM_DUPLICATE_INSERT_FIRST)) { + flags |= HAM_DUPLICATE; + } + + /* allocate temp. storage for a recno key */ + if ((db->get_flags() & HAM_RECORD_NUMBER32) + || (db->get_flags() & HAM_RECORD_NUMBER64)) { + if (flags & HAM_OVERWRITE) { + if (!key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + else { + if (key->flags & HAM_KEY_USER_ALLOC) { + if (!key->data) { + ham_trace(("key->data must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + else { + if (key->data || key->size) { + ham_trace(("key->size must be 0, key->data must be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + } + } + } + + return (db->set_error(db->insert(cursor, cursor->get_txn(), key, + record, flags))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_erase(ham_cursor_t *hcursor, uint32_t flags) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (db->get_flags() & HAM_READ_ONLY) { + ham_trace(("cannot erase from a read-only database")); + return (db->set_error(HAM_WRITE_PROTECTED)); + } + if (flags & HAM_HINT_PREPEND) { + ham_trace(("flags HAM_HINT_PREPEND only allowed in ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + if (flags & HAM_HINT_APPEND) { + ham_trace(("flags HAM_HINT_APPEND only allowed in ham_cursor_insert")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + return (db->set_error(db->erase(cursor, cursor->get_txn(), 0, flags))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_get_duplicate_count(ham_cursor_t *hcursor, uint32_t *count, + uint32_t flags) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (!count) { + ham_trace(("parameter 'count' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + return (db->set_error(db->cursor_get_record_count(cursor, flags, count))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_get_duplicate_position(ham_cursor_t *hcursor, uint32_t *position) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (!position) { + ham_trace(("parameter 'position' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + return (db->set_error(db->cursor_get_duplicate_position(cursor, position))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_get_record_size(ham_cursor_t *hcursor, uint64_t *size) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + if (!size) { + ham_trace(("parameter 'size' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + return (db->set_error(db->cursor_get_record_size(cursor, size))); +} + +ham_status_t HAM_CALLCONV +ham_cursor_close(ham_cursor_t *hcursor) +{ + Database *db; + + if (!hcursor) { + ham_trace(("parameter 'cursor' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Cursor *cursor = (Cursor *)hcursor; + + db = cursor->get_db(); + + ScopedLock lock(db->get_env()->mutex()); + + return (db->set_error(db->cursor_close(cursor))); +} + +void HAM_CALLCONV +ham_set_context_data(ham_db_t *hdb, void *data) +{ + Database *db = (Database *)hdb; + + if (!db) + return; + + ScopedLock lock(db->get_env()->mutex()); + db->set_context_data(data); +} + +void * HAM_CALLCONV +ham_get_context_data(ham_db_t *hdb, ham_bool_t dont_lock) +{ + Database *db = (Database *)hdb; + if (!db) + return (0); + + if (dont_lock) + return (db->get_context_data()); + + ScopedLock lock(db->get_env()->mutex()); + return (db->get_context_data()); +} + +ham_db_t * HAM_CALLCONV +ham_cursor_get_database(ham_cursor_t *hcursor) +{ + if (hcursor) { + Cursor *cursor = (Cursor *)hcursor; + return ((ham_db_t *)cursor->get_db()); + } + return (0); +} + +ham_env_t * HAM_CALLCONV +ham_db_get_env(ham_db_t *hdb) +{ + Database *db = (Database *)hdb; + if (!db) + return (0); + + return ((ham_env_t *)db->get_env()); +} + +ham_status_t HAM_CALLCONV +ham_db_get_key_count(ham_db_t *hdb, ham_txn_t *htxn, uint32_t flags, + uint64_t *keycount) +{ + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + + if (!db) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (flags & ~(HAM_SKIP_DUPLICATES)) { + ham_trace(("parameter 'flag' contains unsupported flag bits: %08x", + flags & (~HAM_SKIP_DUPLICATES))); + return (HAM_INV_PARAMETER); + } + if (!keycount) { + ham_trace(("parameter 'keycount' must not be NULL")); + return (db->set_error(HAM_INV_PARAMETER)); + } + + ScopedLock lock(db->get_env()->mutex()); + + return (db->set_error(db->count(txn, (flags & HAM_SKIP_DUPLICATES) != 0, + keycount))); +} + +void HAM_CALLCONV +ham_set_errhandler(ham_errhandler_fun f) +{ + if (f) + hamsterdb::Globals::ms_error_handler = f; + else + hamsterdb::Globals::ms_error_handler = hamsterdb::default_errhandler; +} + +ham_status_t HAM_CALLCONV +ham_env_get_metrics(ham_env_t *henv, ham_env_metrics_t *metrics) +{ + Environment *env = (Environment *)henv; + if (!env) { + ham_trace(("parameter 'env' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!metrics) { + ham_trace(("parameter 'metrics' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + memset(metrics, 0, sizeof(ham_env_metrics_t)); + metrics->version = HAM_METRICS_VERSION; + + // fill in memory metrics + Memory::get_global_metrics(metrics); + // ... and everything else + return (env->fill_metrics(metrics)); +} + +ham_bool_t HAM_CALLCONV +ham_is_debug() +{ +#ifdef HAM_DEBUG + return (HAM_TRUE); +#else + return (HAM_FALSE); +#endif +} + +ham_bool_t HAM_CALLCONV +ham_is_pro() +{ + return (HAM_FALSE); +} + +uint32_t HAM_CALLCONV +ham_is_pro_evaluation() +{ + return (0); +} diff --git a/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hola.cc b/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hola.cc new file mode 100644 index 0000000000..a5a56a1814 --- /dev/null +++ b/plugins/Dbx_kv/src/hamsterdb/src/5hamsterdb/hola.cc @@ -0,0 +1,704 @@ +/* + * Copyright (C) 2005-2015 Christoph Rupp (chris@crupp.de). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "0root/root.h" + +#include "ham/hamsterdb_ola.h" + +// Always verify that a file of level N does not include headers > N! +#include "1base/error.h" +#include "3btree/btree_visitor.h" +#include "4db/db.h" +#include "4db/db_local.h" + +#ifndef HAM_ROOT_H +# error "root.h was not included" +#endif + +using namespace hamsterdb; + +ham_status_t HAM_CALLCONV +hola_count(ham_db_t *hdb, ham_txn_t *htxn, hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + + result->type = HAM_TYPE_UINT64; + result->u.result_u64 = 0; + + ScopedLock lock(db->get_env()->mutex()); + return (db->set_error(db->count(txn, false, &result->u.result_u64))); +} + +// +// A ScanVisitor for hola_count_if +// +template<typename PodType> +struct CountIfScanVisitor : public ScanVisitor { + CountIfScanVisitor(hola_bool_predicate_t *pred) + : m_count(0), m_pred(pred) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + if (m_pred->predicate_func(key_data, key_size, m_pred->context)) + m_count++; + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + const PodType *p = (const PodType *)key_array; + const PodType *end = &p[key_count]; + for (; p < end; p++) { + if (m_pred->predicate_func(p, sizeof(PodType), m_pred->context)) + m_count++; + } + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + memcpy(&result->u.result_u64, &m_count, sizeof(uint64_t)); + } + + // The counter + uint64_t m_count; + + // The user's predicate + hola_bool_predicate_t *m_pred; +}; + +// +// A ScanVisitor for hola_count_if on binary keys +// +struct CountIfScanVisitorBinary : public ScanVisitor { + CountIfScanVisitorBinary(size_t key_size, hola_bool_predicate_t *pred) + : m_count(0), m_key_size(key_size), m_pred(pred) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + if (m_pred->predicate_func(key_data, key_size, m_pred->context)) + m_count++; + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + assert(m_key_size != HAM_KEY_SIZE_UNLIMITED); + const uint8_t *p = (const uint8_t *)key_array; + const uint8_t *end = &p[key_count * m_key_size]; + for (; p < end; p += m_key_size) { + if (m_pred->predicate_func(p, m_key_size, m_pred->context)) + m_count++; + } + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + memcpy(&result->u.result_u64, &m_count, sizeof(uint64_t)); + } + + // The counter + uint64_t m_count; + + // The key size + uint16_t m_key_size; + + // The user's predicate + hola_bool_predicate_t *m_pred; +}; + +ham_status_t HAM_CALLCONV +hola_count_if(ham_db_t *hdb, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!pred) { + ham_trace(("parameter 'pred' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + result->type = HAM_TYPE_UINT64; + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + visitor.reset(new CountIfScanVisitor<uint8_t>(pred)); + break; + case HAM_TYPE_UINT16: + visitor.reset(new CountIfScanVisitor<uint16_t>(pred)); + break; + case HAM_TYPE_UINT32: + visitor.reset(new CountIfScanVisitor<uint32_t>(pred)); + break; + case HAM_TYPE_UINT64: + visitor.reset(new CountIfScanVisitor<uint64_t>(pred)); + break; + case HAM_TYPE_REAL32: + visitor.reset(new CountIfScanVisitor<float>(pred)); + break; + case HAM_TYPE_REAL64: + visitor.reset(new CountIfScanVisitor<double>(pred)); + break; + case HAM_TYPE_BINARY: + visitor.reset(new CountIfScanVisitorBinary(db->config().key_size, + pred)); + break; + default: + ham_assert(!"shouldn't be here"); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), false); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} + +ham_status_t HAM_CALLCONV +hola_count_distinct(ham_db_t *hdb, ham_txn_t *htxn, hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + Database *db = (Database *)hdb; + Transaction *txn = (Transaction *)htxn; + + result->type = HAM_TYPE_UINT64; + result->u.result_u64 = 0; + + ScopedLock lock(db->get_env()->mutex()); + return (db->set_error(db->count(txn, true, &result->u.result_u64))); +} + +ham_status_t HAM_CALLCONV +hola_count_distinct_if(ham_db_t *hdb, ham_txn_t *txn, + hola_bool_predicate_t *pred, hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!pred) { + ham_trace(("parameter 'pred' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + result->type = HAM_TYPE_UINT64; + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + visitor.reset(new CountIfScanVisitor<uint8_t>(pred)); + break; + case HAM_TYPE_UINT16: + visitor.reset(new CountIfScanVisitor<uint16_t>(pred)); + break; + case HAM_TYPE_UINT32: + visitor.reset(new CountIfScanVisitor<uint32_t>(pred)); + break; + case HAM_TYPE_UINT64: + visitor.reset(new CountIfScanVisitor<uint64_t>(pred)); + break; + case HAM_TYPE_REAL32: + visitor.reset(new CountIfScanVisitor<float>(pred)); + break; + case HAM_TYPE_REAL64: + visitor.reset(new CountIfScanVisitor<double>(pred)); + break; + case HAM_TYPE_BINARY: + visitor.reset(new CountIfScanVisitorBinary(db->config().key_size, + pred)); + break; + default: + ham_assert(!"shouldn't be here"); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), true); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} + +// +// A ScanVisitor for hola_average +// +template<typename PodType, typename ResultType> +struct AverageScanVisitor : public ScanVisitor { + AverageScanVisitor() + : m_sum(0), m_count(0) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + ham_assert(key_size == sizeof(PodType)); + + m_sum += *(const PodType *)key_data * duplicate_count; + m_count++; + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + const PodType *p = (const PodType *)key_array; + const PodType *end = &p[key_count]; + for (; p < end; p++) + m_sum += *p; + m_count += key_count; + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + ResultType res = m_sum / m_count; + memcpy(&result->u.result_u64, &res, sizeof(uint64_t)); + } + + // The sum of all keys + ResultType m_sum; + + // For counting the keys + uint64_t m_count; +}; + +ham_status_t HAM_CALLCONV +hola_average(ham_db_t *hdb, ham_txn_t *txn, hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageScanVisitor<uint8_t, uint64_t>()); + break; + case HAM_TYPE_UINT16: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageScanVisitor<uint16_t, uint64_t>()); + break; + case HAM_TYPE_UINT32: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageScanVisitor<uint32_t, uint64_t>()); + break; + case HAM_TYPE_UINT64: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageScanVisitor<uint64_t, uint64_t>()); + break; + case HAM_TYPE_REAL32: + result->type = HAM_TYPE_REAL64; + visitor.reset(new AverageScanVisitor<float, double>()); + break; + case HAM_TYPE_REAL64: + result->type = HAM_TYPE_REAL64; + visitor.reset(new AverageScanVisitor<double, double>()); + break; + default: + ham_trace(("hola_avg* can only be applied to numerical data")); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), false); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} + +// +// A ScanVisitor for hola_average_if +// +template<typename PodType, typename ResultType> +struct AverageIfScanVisitor : public ScanVisitor { + AverageIfScanVisitor(hola_bool_predicate_t *pred) + : m_sum(0), m_count(0), m_pred(pred) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + ham_assert(key_size == sizeof(PodType)); + + if (m_pred->predicate_func(key_data, key_size, m_pred->context)) { + m_sum += *(const PodType *)key_data * duplicate_count; + m_count++; + } + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + const PodType *p = (const PodType *)key_array; + const PodType *end = &p[key_count]; + for (; p < end; p++) { + if (m_pred->predicate_func(p, sizeof(PodType), m_pred->context)) { + m_sum += *p; + m_count++; + } + } + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + ResultType res = m_sum / m_count; + memcpy(&result->u.result_u64, &res, sizeof(uint64_t)); + } + + // The sum of all keys + ResultType m_sum; + + // For counting the keys + uint64_t m_count; + + // The user's predicate function + hola_bool_predicate_t *m_pred; +}; + +ham_status_t HAM_CALLCONV +hola_average_if(ham_db_t *hdb, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!pred) { + ham_trace(("parameter 'pred' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageIfScanVisitor<uint8_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT16: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageIfScanVisitor<uint16_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT32: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageIfScanVisitor<uint32_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT64: + result->type = HAM_TYPE_UINT64; + visitor.reset(new AverageIfScanVisitor<uint64_t, uint64_t>(pred)); + break; + case HAM_TYPE_REAL32: + result->type = HAM_TYPE_REAL64; + visitor.reset(new AverageIfScanVisitor<float, double>(pred)); + break; + case HAM_TYPE_REAL64: + result->type = HAM_TYPE_REAL64; + visitor.reset(new AverageIfScanVisitor<double, double>(pred)); + break; + default: + ham_trace(("hola_avg* can only be applied to numerical data")); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), false); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} + +// +// A ScanVisitor for hola_sum +// +template<typename PodType, typename ResultType> +struct SumScanVisitor : public ScanVisitor { + SumScanVisitor() + : m_sum(0) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + ham_assert(key_size == sizeof(PodType)); + m_sum += *(const PodType *)key_data * duplicate_count; + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + const PodType *p = (const PodType *)key_array; + const PodType *end = &p[key_count]; + const int kMax = 8; + ResultType sums[kMax] = {0}; + for (; p + kMax < end; p += kMax) { +#if defined __GNUC__ + __builtin_prefetch(((char *)p) + kMax * sizeof(PodType)); +#endif + sums[0] += p[0]; + sums[1] += p[1]; + sums[2] += p[2]; + sums[3] += p[3]; + sums[4] += p[4]; + sums[5] += p[5]; + sums[6] += p[6]; + sums[7] += p[7]; + } + for (; p < end; p++) + m_sum += *p; + for (int i = 0; i < kMax; i++) + m_sum += sums[i]; + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + memcpy(&result->u.result_u64, &m_sum, sizeof(uint64_t)); + } + + // The sum of all keys + ResultType m_sum; +}; + +ham_status_t HAM_CALLCONV +hola_sum(ham_db_t *hdb, ham_txn_t *txn, hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'hdb' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumScanVisitor<uint8_t, uint64_t>()); + break; + case HAM_TYPE_UINT16: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumScanVisitor<uint16_t, uint64_t>()); + break; + case HAM_TYPE_UINT32: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumScanVisitor<uint32_t, uint64_t>()); + break; + case HAM_TYPE_UINT64: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumScanVisitor<uint64_t, uint64_t>()); + break; + case HAM_TYPE_REAL32: + result->type = HAM_TYPE_REAL64; + visitor.reset(new SumScanVisitor<float, double>()); + break; + case HAM_TYPE_REAL64: + result->type = HAM_TYPE_REAL64; + visitor.reset(new SumScanVisitor<double, double>()); + break; + default: + ham_trace(("hola_sum* can only be applied to numerical data")); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), false); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} + +// +// A ScanVisitor for hola_sum_if +// +template<typename PodType, typename ResultType> +struct SumIfScanVisitor : public ScanVisitor { + SumIfScanVisitor(hola_bool_predicate_t *pred) + : m_sum(0), m_pred(pred) { + } + + // Operates on a single key + virtual void operator()(const void *key_data, uint16_t key_size, + size_t duplicate_count) { + ham_assert(key_size == sizeof(PodType)); + + if (m_pred->predicate_func(key_data, key_size, m_pred->context)) + m_sum += *(const PodType *)key_data * duplicate_count; + } + + // Operates on an array of keys + virtual void operator()(const void *key_array, size_t key_count) { + const PodType *p = (const PodType *)key_array; + const PodType *end = &p[key_count]; + for (; p < end; p++) { + if (m_pred->predicate_func(p, sizeof(PodType), m_pred->context)) + m_sum += *p; + } + } + + // Assigns the result to |result| + virtual void assign_result(hola_result_t *result) { + memcpy(&result->u.result_u64, &m_sum, sizeof(uint64_t)); + } + + // The sum of all keys + ResultType m_sum; + + // The user's predicate function + hola_bool_predicate_t *m_pred; +}; + +ham_status_t HAM_CALLCONV +hola_sum_if(ham_db_t *hdb, ham_txn_t *txn, hola_bool_predicate_t *pred, + hola_result_t *result) +{ + if (!hdb) { + ham_trace(("parameter 'db' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!pred) { + ham_trace(("parameter 'pred' must not be NULL")); + return (HAM_INV_PARAMETER); + } + if (!result) { + ham_trace(("parameter 'result' must not be NULL")); + return (HAM_INV_PARAMETER); + } + + // Remote databases are not yet supported + LocalDatabase *db = dynamic_cast<LocalDatabase *>((Database *)hdb); + if (!db) { + ham_trace(("hola_* functions are not yet supported for remote databases")); + return (HAM_INV_PARAMETER); + } + + std::auto_ptr<ScanVisitor> visitor; + result->u.result_u64 = 0; + + switch (db->config().key_type) { + case HAM_TYPE_UINT8: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumIfScanVisitor<uint8_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT16: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumIfScanVisitor<uint16_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT32: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumIfScanVisitor<uint32_t, uint64_t>(pred)); + break; + case HAM_TYPE_UINT64: + result->type = HAM_TYPE_UINT64; + visitor.reset(new SumIfScanVisitor<uint64_t, uint64_t>(pred)); + break; + case HAM_TYPE_REAL32: + result->type = HAM_TYPE_REAL64; + visitor.reset(new SumIfScanVisitor<float, double>(pred)); + break; + case HAM_TYPE_REAL64: + result->type = HAM_TYPE_REAL64; + visitor.reset(new SumIfScanVisitor<double, double>(pred)); + break; + default: + ham_trace(("hola_sum* can only be applied to numerical data")); + return (HAM_INV_PARAMETER); + } + + ScopedLock lock(db->get_env()->mutex()); + ham_status_t st = db->scan((Transaction *)txn, visitor.get(), false); + if (st == 0) + visitor->assign_result(result); + return (db->set_error(st)); +} diff --git a/plugins/Dbx_kv/src/init.cpp b/plugins/Dbx_kv/src/init.cpp new file mode 100644 index 0000000000..7649924668 --- /dev/null +++ b/plugins/Dbx_kv/src/init.cpp @@ -0,0 +1,152 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+#if _MSC_VER >= 1800 + #ifdef _DEBUG + #pragma comment(lib, "libboost_thread-vc120-mt-gd-1_57.lib") + #pragma comment(lib, "libboost_system-vc120-mt-gd-1_57.lib") + #else + #pragma comment(lib, "libboost_thread-vc120-mt-1_57.lib") + #pragma comment(lib, "libboost_system-vc120-mt-1_57.lib") + #endif +#else + #ifdef _DEBUG + #pragma comment(lib, "libboost_thread-vc100-mt-gd-1_57.lib") + #pragma comment(lib, "libboost_system-vc100-mt-gd-1_57.lib") + #else + #pragma comment(lib, "libboost_thread-vc100-mt-1_57.lib") + #pragma comment(lib, "libboost_system-vc100-mt-1_57.lib") + #endif +#endif +
+int hLangpack;
+
+static PLUGININFOEX pluginInfo =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __AUTHOREMAIL,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE | STATIC_PLUGIN,
+ // {7C3D0A33-2646-4001-9107-F35EA299D292}
+ { 0x7c3d0a33, 0x2646, 0x4001, { 0x91, 0x7, 0xf3, 0x5e, 0xa2, 0x99, 0xd2, 0x92 } }
+};
+
+HINSTANCE g_hInst = NULL;
+
+LIST<CDbxKV> g_Dbs(1, HandleKeySortT);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// returns 0 if the profile is created, EMKPRF*
+static int makeDatabase(const TCHAR *profile)
+{
+ std::auto_ptr<CDbxKV> db(new CDbxKV(profile, 0));
+ return db->Create();
+}
+
+// returns 0 if the given profile has a valid header
+static int grokHeader(const TCHAR *profile)
+{
+ std::auto_ptr<CDbxKV> db(new CDbxKV(profile, DBMODE_SHARED | DBMODE_READONLY));
+ return db->Check();
+}
+
+// returns 0 if all the APIs are injected otherwise, 1
+static MIDatabase* LoadDatabase(const TCHAR *profile, BOOL bReadOnly)
+{
+ // set the memory, lists & UTF8 manager
+ mir_getLP(&pluginInfo);
+
+ std::auto_ptr<CDbxKV> db(new CDbxKV(profile, (bReadOnly) ? DBMODE_READONLY : 0));
+ if (db->Load(false) != ERROR_SUCCESS)
+ return NULL;
+
+ g_Dbs.insert(db.get());
+ return db.release();
+}
+
+static int UnloadDatabase(MIDatabase *db)
+{
+ g_Dbs.remove((CDbxKV*)db);
+ delete (CDbxKV*)db;
+ return 0;
+}
+
+MIDatabaseChecker* CheckDb(const TCHAR *profile, int *error)
+{
+ std::auto_ptr<CDbxKV> db(new CDbxKV(profile, DBMODE_READONLY));
+ if (db->Load(true) != ERROR_SUCCESS) {
+ *error = ERROR_ACCESS_DENIED;
+ return NULL;
+ }
+
+ if (db->PrepareCheck(error))
+ return NULL;
+
+ return db.release();
+}
+
+static DATABASELINK dblink =
+{
+ sizeof(DATABASELINK),
+ "dbx_kv",
+ _T("Hamsterdb database driver"),
+ makeDatabase,
+ grokHeader,
+ LoadDatabase,
+ UnloadDatabase,
+ CheckDb
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD)
+{
+ return &pluginInfo;
+}
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_DATABASE, MIID_LAST };
+
+extern "C" __declspec(dllexport) int Load(void)
+{
+ RegisterDatabasePlugin(&dblink);
+ return 0;
+}
+
+extern "C" __declspec(dllexport) int Unload(void)
+{
+ return 0;
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD, LPVOID)
+{
+ g_hInst = hInstDLL;
+ return TRUE;
+}
diff --git a/plugins/Dbx_kv/src/resource.h b/plugins/Dbx_kv/src/resource.h new file mode 100644 index 0000000000..be74c3e5e8 --- /dev/null +++ b/plugins/Dbx_kv/src/resource.h @@ -0,0 +1,32 @@ +//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by D:\Myranda\plugins\Db3x_mmap\res\db3x_mmap.rc
+//
+
+#define IDREMOVE 3
+
+#define IDI_ICONPASS 100
+#define IDI_LOGO 101
+#define IDD_LOGIN 102
+#define IDD_NEWPASS 103
+#define IDD_CHANGEPASS 104
+#define IDD_OPTIONS 105
+#define IDC_HEADERBAR 1001
+#define IDC_LANG 1002
+#define IDC_USERPASS 1003
+#define IDC_USERPASS1 1004
+#define IDC_USERPASS2 1005
+#define IDC_OLDPASS 1006
+#define IDC_STANDARD 1007
+#define IDC_TOTAL 1008
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 106
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1009
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/plugins/Dbx_kv/src/stdafx.cpp b/plugins/Dbx_kv/src/stdafx.cpp new file mode 100644 index 0000000000..048b14e9d2 --- /dev/null +++ b/plugins/Dbx_kv/src/stdafx.cpp @@ -0,0 +1,18 @@ +/*
+Copyright (C) 2012-15 Miranda NG project (http://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "commonheaders.h"
\ No newline at end of file diff --git a/plugins/Dbx_kv/src/ui.cpp b/plugins/Dbx_kv/src/ui.cpp new file mode 100644 index 0000000000..a4ba3d12c5 --- /dev/null +++ b/plugins/Dbx_kv/src/ui.cpp @@ -0,0 +1,340 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org)
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+
+struct DlgChangePassParam
+{
+ CDbxKV *db;
+ TCHAR newPass[100];
+ int wrongPass;
+};
+
+#define MS_DB_CHANGEPASSWORD "DB/UI/ChangePassword"
+
+static IconItem iconList[] =
+{
+ { LPGEN("Logo"), "logo", IDI_LOGO },
+ { LPGEN("Password"), "password", IDI_ICONPASS }
+};
+
+static HGENMENU hSetPwdMenu;
+
+static UINT oldLangID;
+void LanguageChanged(HWND hwndDlg)
+{
+ UINT LangID = (UINT)GetKeyboardLayout(0);
+ char Lang[3] = { 0 };
+ if (LangID != oldLangID) {
+ oldLangID = LangID;
+ GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2);
+ Lang[0] = toupper(Lang[0]);
+ Lang[1] = tolower(Lang[1]);
+ SetDlgItemTextA(hwndDlg, IDC_LANG, Lang);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR CALLBACK sttEnterPassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DlgChangePassParam *param = (DlgChangePassParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(g_hInst, MAKEINTRESOURCE(iconList[0].defIconID)));
+
+ param = (DlgChangePassParam*)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ if (param->wrongPass) {
+ if (param->wrongPass > 2) {
+ HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_USERPASS);
+ EnableWindow(hwndCtrl, FALSE);
+ hwndCtrl = GetDlgItem(hwndDlg, IDOK);
+ EnableWindow(hwndCtrl, FALSE);
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Too many errors!"));
+ }
+ else SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is not correct!"));
+ }
+ else SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Please type in your password"));
+
+ oldLangID = 0;
+ SetTimer(hwndDlg, 1, 200, NULL);
+ LanguageChanged(hwndDlg);
+ return TRUE;
+
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (BOOL)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ return FALSE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+
+ case IDOK:
+ GetDlgItemText(hwndDlg, IDC_USERPASS, param->newPass, SIZEOF(param->newPass));
+ EndDialog(hwndDlg, IDOK);
+ }
+ break;
+
+ case WM_TIMER:
+ LanguageChanged(hwndDlg);
+ return FALSE;
+
+ case WM_DESTROY:
+ KillTimer(hwndDlg, 1);
+ DestroyIcon((HICON)SendMessage(hwndDlg, WM_GETICON, ICON_SMALL, 0));
+ }
+
+ return FALSE;
+}
+
+bool CDbxKV::EnterPassword(const BYTE *pKey, const size_t keyLen)
+{
+ DlgChangePassParam param = { this };
+ while (true) {
+ // Esc pressed
+ if (IDOK != DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_LOGIN), 0, sttEnterPassword, (LPARAM)¶m))
+ return false;
+
+ m_crypto->setPassword(ptrA(mir_utf8encodeT(param.newPass)));
+ if (m_crypto->setKey(pKey, keyLen)) {
+ m_bUsesPassword = true;
+ SecureZeroMemory(¶m, sizeof(param));
+ return true;
+ }
+
+ param.wrongPass++;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool CheckOldPassword(HWND hwndDlg, CDbxKV *db)
+{
+ if (db->usesPassword()) {
+ TCHAR buf[100];
+ GetDlgItemText(hwndDlg, IDC_OLDPASS, buf, SIZEOF(buf));
+ ptrA oldPass(mir_utf8encodeT(buf));
+ if (!db->m_crypto->checkPassword(oldPass)) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Wrong old password entered!"));
+ return false;
+ }
+ }
+ return true;
+}
+
+static INT_PTR CALLBACK sttChangePassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DlgChangePassParam *param = (DlgChangePassParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ TCHAR buf[100];
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)Skin_GetIconByHandle(iconList[0].hIcolib, true));
+
+ param = (DlgChangePassParam*)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ oldLangID = 0;
+ SetTimer(hwndDlg, 1, 200, NULL);
+ LanguageChanged(hwndDlg);
+ return TRUE;
+
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (BOOL)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ return FALSE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+
+ case IDREMOVE:
+ if (!CheckOldPassword(hwndDlg, param->db)) {
+ LBL_Error:
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_NCPAINT, 0, 0);
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS1, "");
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS2, "");
+ }
+ else {
+ // param->db->WriteSignature(dbSignatureU);
+ param->db->SetPassword(NULL);
+ param->db->StoreKey();
+ EndDialog(hwndDlg, IDREMOVE);
+ }
+ break;
+
+ case IDOK:
+ TCHAR buf2[100];
+ GetDlgItemText(hwndDlg, IDC_USERPASS1, buf2, SIZEOF(buf2));
+ if (_tcslen(buf2) < 3) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is too short!"));
+ goto LBL_Error;
+ }
+
+ GetDlgItemText(hwndDlg, IDC_USERPASS2, buf, SIZEOF(buf));
+ if (_tcscmp(buf2, buf)) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Passwords do not match!"));
+ goto LBL_Error;
+ }
+
+ if (!CheckOldPassword(hwndDlg, param->db))
+ goto LBL_Error;
+
+ // param->db->WriteSignature(dbSignatureE);
+ param->db->SetPassword(buf2);
+ param->db->StoreKey();
+ SecureZeroMemory(buf2, sizeof(buf2));
+ EndDialog(hwndDlg, IDOK);
+ }
+ break;
+
+ case WM_TIMER:
+ LanguageChanged(hwndDlg);
+ return FALSE;
+
+ case WM_DESTROY:
+ KillTimer(hwndDlg, 1);
+ Skin_ReleaseIcon((HICON)SendMessage(hwndDlg, WM_GETICON, ICON_SMALL, 0));
+ }
+
+ return FALSE;
+}
+
+static INT_PTR ChangePassword(void* obj, WPARAM, LPARAM)
+{
+ CDbxKV *db = (CDbxKV*)obj;
+ DlgChangePassParam param = { db };
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(db->usesPassword() ? IDD_CHANGEPASS : IDD_NEWPASS), 0, sttChangePassword, (LPARAM)¶m);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR CALLBACK DlgProcOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CDbxKV *db = (CDbxKV *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ db = (CDbxKV*)lParam;
+ CheckRadioButton(hwndDlg, IDC_STANDARD, IDC_TOTAL, IDC_STANDARD + db->isEncrypted());
+ return TRUE;
+
+ case WM_COMMAND:
+ if (HIWORD(wParam) == BN_CLICKED && (HWND)lParam == GetFocus()) {
+ if (LOWORD(wParam) == IDC_USERPASS)
+ CallService(MS_DB_CHANGEPASSWORD, 0, 0);
+ else
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == PSN_APPLY) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_TOTAL) != (UINT)db->isEncrypted()) {
+ db->ToggleEncryption();
+ CheckRadioButton(hwndDlg, IDC_STANDARD, IDC_TOTAL, IDC_STANDARD + db->isEncrypted());
+ }
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+static int OnOptionsInit(PVOID obj, WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { sizeof(odp) };
+ odp.position = -790000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pszTitle = LPGEN("Database");
+ odp.pfnDlgProc = DlgProcOptions;
+ odp.dwInitParam = (LPARAM)obj;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxKV::UpdateMenuItem()
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIM_NAME;
+ mi.icolibItem = iconList[1].hIcolib;
+ mi.pszName = GetMenuTitle();
+ Menu_ModifyItem(hSetPwdMenu, &mi);
+}
+
+static int OnModulesLoaded(PVOID obj, WPARAM, LPARAM)
+{
+ CDbxKV *db = (CDbxKV*)obj;
+
+ Icon_Register(g_hInst, LPGEN("Database"), iconList, SIZEOF(iconList), "mmap");
+
+ HookEventObj(ME_OPT_INITIALISE, OnOptionsInit, db);
+
+ // main menu item
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.pszName = LPGEN("Database");
+ mi.position = 500000000;
+ mi.flags = CMIF_ROOTHANDLE;
+ mi.icolibItem = iconList[0].hIcolib;
+ HGENMENU hMenuRoot = Menu_AddMainMenuItem(&mi);
+
+ mi.icolibItem = iconList[1].hIcolib;
+ mi.pszName = db->GetMenuTitle();
+ mi.hParentMenu = hMenuRoot;
+ mi.pszService = MS_DB_CHANGEPASSWORD;
+ hSetPwdMenu = Menu_AddMainMenuItem(&mi);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxKV::InitDialogs()
+{
+ hService = CreateServiceFunctionObj(MS_DB_CHANGEPASSWORD, ChangePassword, this);
+ hHook = HookEventObj(ME_SYSTEM_MODULESLOADED, OnModulesLoaded, this);
+}
diff --git a/plugins/Dbx_kv/src/version.h b/plugins/Dbx_kv/src/version.h new file mode 100644 index 0000000000..5f4860912c --- /dev/null +++ b/plugins/Dbx_kv/src/version.h @@ -0,0 +1,14 @@ +#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 95
+#define __RELEASE_NUM 4
+#define __BUILD_NUM 1
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "Miranda NG Hamsterdb database driver"
+#define __FILENAME "Dbx_kv.dll"
+#define __DESCRIPTION "Provides Miranda database support: global settings, contacts, history, settings per contact."
+#define __AUTHOR "Miranda-NG project"
+#define __AUTHOREMAIL "ghazan@miranda.im"
+#define __AUTHORWEB "http://miranda-ng.org/p/Dbx_kv/"
+#define __COPYRIGHT "© 2015 Miranda NG project"
|