-- -- -- package Copyright (c) Dmitry A. Kazakov -- -- Persistent.Memory_Pools.Streams. Luebeck -- -- External_B_Tree.Generic_Table Autumn, 2014 -- -- Implementation -- -- Last revision : 18:00 18 Aug 2022 -- -- -- -- This library 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 library -- -- 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 library; if not, write to the Free Software Foundation, -- -- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -- -- -- -- As a special exception, if other files instantiate generics from -- -- this unit, or you link this unit with other files to produce an -- -- executable, this unit does not by itself cause the resulting -- -- executable to be covered by the GNU General Public License. This -- -- exception does not however invalidate any other reasons why the -- -- executable file might be covered by the GNU Public License. -- --____________________________________________________________________-- with Ada.IO_Exceptions; use Ada.IO_Exceptions; with Ada.Tags; use Ada.Tags; with Ada.Unchecked_Deallocation; package body Persistent.Memory_Pools.Streams.External_B_Tree. Generic_Table is Values_Offs : constant Block_Offset := (Key_Index'Pos (Key_Index'Last) + 1) * 8; Not_Empty : constant String := "Table is not empty"; procedure Add ( Container : in out Table; Keys : Keys_Tuple; Values : Values_Tuple ) is Lock : Holder (Container.Pool); begin Unchecked_Add (Container, Keys, Values); end Add; function Compare ( Container : Table; Index : Key_Index; Left : Byte_Index; Right : Byte_Index ) return Precedence is begin if Left = Right then return Equal; elsif Left < Right then return Less; else return Greater; end if; end Compare; procedure Erase (Container : in out Table) is Lock : Holder (Container.Pool); begin for Index in Key_Index'Range loop Unchecked_Erase (Container.Roots (Index).all); end loop; if Container.Address /= 0 then Unchecked_Deallocate (Container.Pool.all, Container.Address); Container.Address := 0; end if; end Erase; procedure Finalize (Container : in out Table) is use Ada.Finalization; procedure Free is new Ada.Unchecked_Deallocation (B_Tree'Class, B_Tree_Ptr); begin if Is_Writable (Container.Pool.File.all) then Erase (Container); for Index in Key_Index'Range loop Free (Container.Roots (Index)); end loop; end if; Finalize (Limited_Controlled (Container)); end Finalize; function Find ( Container : Table; Index : Key_Index; Key : Byte_Index ) return Row_Ptr is Item : constant Item_Ptr := Find (Container.Roots (Index).all, Key); begin if Item = No_Item then return No_Row; else return (Container.Self, Item.Node, Item.Index); end if; end Find; function Get ( Container : Table; Index : Key_Index; Key : Byte_Index ) return Values_Tuple is Lock : Holder (Container.Pool); Item : Byte_Index; Values : Values_Tuple; begin Item := Unchecked_Get (Container.Roots (Index).all, Key); declare Block : Block_Type renames Load (Container.Pool.File, Item).all; Offset : Block_Offset := Get_Offset (Item) + Values_Offs; begin for Index in Values'Range loop Values (Index) := Get (Block, Offset); Offset := Offset + 8; end loop; end; return Values; end Get; function Get ( Container : Table; Index : Key_Index; Key : Byte_Index; Column : Value_Index ) return Byte_Index is Lock : Holder (Container.Pool); Item : Byte_Index; begin Item := Unchecked_Get (Container.Roots (Index).all, Key); return Get ( Load (Container.Pool.File, Item).all, ( Get_Offset (Item) + Values_Offs + Value_Index'Pos (Column) * 8 ) ); end Get; function Get_Bucket_Address (Row : Row_Ptr) return Byte_Index is begin return Row.Node; end Get_Bucket_Address; function Get_Bucket_Size (Row : Row_Ptr) return Natural is begin if Row.Node = 0 then Raise_Exception (Constraint_Error'Identity, Bad_Row); else return Get_Bucket_Size ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); end if; end Get_Bucket_Size; function Get_First ( Container : Table; Index : Key_Index ) return Row_Ptr is Item : constant Item_Ptr := Get_First (Container.Roots (Index).all); begin if Item = No_Item then return No_Row; else return (Container.Self, Item.Node, Item.Index); end if; end Get_First; function Get_Index (Row : Row_Ptr) return Positive is begin if Row.Node = 0 then Raise_Exception (Constraint_Error'Identity, Invalid_Item); else return Row.Index; end if; end Get_Index; function Get_Key ( Row : Row_Ptr; Index : Key_Index ) return Byte_Index is begin return Get_Key ( Item_Ptr' ( Tree => Row.Table.Roots (Index), Node => Row.Node, Index => Row.Index ) ); end Get_Key; procedure Get_Keys ( Block : Block_Type; Offset : in out Block_Offset; Keys : out Keys_Tuple ) is begin for Index in Keys'Range loop Keys (Index) := Get (Block, Offset); Offset := Offset + 8; end loop; end Get_Keys; function Get_Keys (Row : Row_Ptr) return Keys_Tuple is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Load (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This); Keys : Keys_Tuple; begin Get_Keys (Block, Offset, Keys); return Keys; end Get_Keys; function Get_Last ( Container : Table; Index : Key_Index ) return Row_Ptr is Item : constant Item_Ptr := Get_Last (Container.Roots (Index).all); begin if Item = No_Item then return No_Row; else return (Container.Self, Item.Node, Item.Index); end if; end Get_Last; function Get_Length (Block : Block_Type) return Natural is pragma Inline (Get_Length); begin return Natural (Unsigned_16'(Get (Block, Length_Offset))); end Get_Length; function Get_Next ( Row : Row_Ptr; Index : Key_Index ) return Row_Ptr is Item : constant Item_Ptr := Get_Next ( Item_Ptr' ( Tree => Row.Table.Roots (Index), Node => Row.Node, Index => Row.Index ) ); begin if Item = No_Item then return No_Row; else return (Row.Table, Item.Node, Item.Index); end if; end Get_Next; function Get_Previous ( Row : Row_Ptr; Index : Key_Index ) return Row_Ptr is Item : constant Item_Ptr := Get_Previous ( Item_Ptr' ( Tree => Row.Table.Roots (Index), Node => Row.Node, Index => Row.Index ) ); begin if Item = No_Item then return No_Row; else return (Row.Table, Item.Node, Item.Index); end if; end Get_Previous; function Get_Root ( Row : Row_Ptr; Index : Key_Index ) return Row_Ptr is Item : constant Item_Ptr := Get_Root ( Item_Ptr' ( Tree => Row.Table.Roots (Index), Node => Row.Node, Index => Row.Index ) ); begin if Item = No_Item then return No_Row; else return (Row.Table, Item.Node, Item.Index); end if; end Get_Root; function Get_Root_Address (Container : Table) return Byte_Index is Lock : Holder (Container.Pool); begin if Container.Roots (Key_Index'First).Root_Bucket /= 0 then if Container.Address = 0 then Container.Self.Address := Unchecked_Allocate ( Container.Pool.all, Container.Roots'Length * 8 ); end if; declare Block : Block_Type renames Update ( Container.Pool.File, Container.Address ) .all; Offset : Block_Offset := Get_Offset (Container.Address); begin for Index in Container.Roots'Range loop Put (Block, Offset, Container.Roots (Index).Root_Bucket); Offset := Offset + 8; end loop; end; end if; return Container.Address; end Get_Root_Address; function Get_Value ( Row : Row_Ptr; Column : Value_Index ) return Byte_Index is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); begin return Get ( Load (Row.Table.Pool.File, This).all, ( Get_Offset (This) + Values_Offs + Value_Index'Pos (Column) * 8 ) ); end Get_Value; procedure Get_Values ( Block : Block_Type; Offset : in out Block_Offset; Values : out Values_Tuple ) is begin for Index in Values'Range loop Values (Index) := Get (Block, Offset); Offset := Offset + 8; end loop; end Get_Values; function Get_Values (Row : Row_Ptr) return Values_Tuple is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Load (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; Values : Values_Tuple; begin Get_Values (Block, Offset, Values); return Values; end Get_Values; function Inf ( Container : Table; Index : Key_Index; Key : Byte_Index ) return Row_Ptr is Item : constant Item_Ptr := Inf (Container.Roots (Index).all, Key); begin if Item = No_Item then return No_Row; else return (Container.Self, Item.Node, Item.Index); end if; end Inf; procedure Initialize (Container : in out Table) is use Ada.Finalization; begin Initialize (Limited_Controlled (Container)); for Index in Container.Roots'Range loop Container.Roots (Index) := new B_Tree (Container.Pool.all'Unchecked_Access); end loop; end Initialize; function Is_Empty (Container : Table) return Boolean is begin return Is_Empty (Container.Roots (Key_Index'First).all); end Is_Empty; function Is_In ( Container : Table; Index : Key_Index; Key : Byte_Index ) return Boolean is begin return Is_In (Container.Roots (Index).all, Key); end Is_In; procedure Remove (Row : in out Row_Ptr) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Load (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; Keys : Keys_Tuple; begin Get_Keys (Block, Offset, Keys); for Index in Keys'Range loop Unchecked_Remove (Row.Table.Roots (Index).all, Keys (Index)); end loop; Unchecked_Deallocate (Row.Table.Pool.all, This); Row := No_Row; end Remove; procedure Remove ( Row : in out Row_Ptr; Values : out Values_Tuple ) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Load (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; Keys : Keys_Tuple; begin Get_Keys (Block, Offset, Keys); Get_Values (Block, Offset, Values); for Index in Keys'Range loop Unchecked_Remove (Row.Table.Roots (Index).all, Keys (Index)); end loop; Unchecked_Deallocate (Row.Table.Pool.all, This); Row := No_Row; end Remove; procedure Remove ( Container : in out Table; Index : Key_Index; Key : Byte_Index ) is Lock : Holder (Container.Pool); Item : constant Item_Ptr := Unchecked_Find (Container.Roots (Index).all, Key); begin if Item = External_B_Tree.No_Item then return; end if; declare This : constant Byte_Index := Unchecked_Get_Value (Item); Block : Block_Type renames Load (Container.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; Keys : Keys_Tuple; begin Get_Keys (Block, Offset, Keys); for Index in Keys'Range loop Unchecked_Remove ( Container.Roots (Index).all, Keys (Index) ); end loop; Unchecked_Deallocate (Container.Pool.all, This); end; end Remove; procedure Remove ( Container : in out Table; Index : Key_Index; Key : Byte_Index; Values : out Values_Tuple ) is Lock : Holder (Container.Pool); Item : constant Item_Ptr := Unchecked_Find (Container.Roots (Index).all, Key); begin if Item = External_B_Tree.No_Item then Values := (others => 0); return; end if; declare This : constant Byte_Index := Unchecked_Get_Value (Item); Block : Block_Type renames Load (Container.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; Keys : Keys_Tuple; begin Get_Keys (Block, Offset, Keys); Get_Values (Block, Offset, Values); for Index in Keys'Range loop Unchecked_Remove ( Container.Roots (Index).all, Keys (Index) ); end loop; Unchecked_Deallocate (Container.Pool.all, This); end; end Remove; procedure Remove ( Container : in out Table; Index : Key_Index; Key : Byte_Index; Keys : out Keys_Tuple; Values : out Values_Tuple ) is Lock : Holder (Container.Pool); Item : constant Item_Ptr := Unchecked_Find (Container.Roots (Index).all, Key); begin if Item = External_B_Tree.No_Item then Keys := (others => 0); Values := (others => 0); return; end if; declare This : constant Byte_Index := Unchecked_Get_Value (Item); Block : Block_Type renames Load (Container.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; begin Get_Keys (Block, Offset, Keys); Get_Values (Block, Offset, Values); for Index in Keys'Range loop Unchecked_Remove ( Container.Roots (Index).all, Keys (Index) ); end loop; Unchecked_Deallocate (Container.Pool.all, This); end; end Remove; procedure Replace ( Row : Row_Ptr; Column : Value_Index; Value : Byte_Index ) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Update (Row.Table.Pool.File, This).all; Offset : constant Block_Offset := ( Get_Offset (This) + Values_Offs + Value_Index'Pos (Column) * 8 ); begin Put (Block, Offset, Value); end Replace; procedure Replace ( Row : Row_Ptr; Column : Value_Index; Value : Byte_Index; Replaced : out Byte_Index ) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Update (Row.Table.Pool.File, This).all; Offset : constant Block_Offset := ( Get_Offset (This) + Values_Offs + Value_Index'Pos (Column) * 8 ); begin Replaced := Get (Block, Offset); Put (Block, Offset, Value); end Replace; procedure Replace (Row : Row_Ptr; Values : Values_Tuple) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Update (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; begin Set_Values (Block, Offset, Values); end Replace; procedure Replace ( Row : Row_Ptr; Values : Values_Tuple; Replaced : out Values_Tuple ) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Update (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This) + Values_Offs; begin Get_Values (Block, Offset, Replaced); Offset := Get_Offset (This) + Values_Offs; Set_Values (Block, Offset, Values); end Replace; procedure Replace ( Container : in out Table; Keys : Keys_Tuple; Values : Values_Tuple ) is Lock : Holder (Container.Pool); Item : constant Item_Ptr := Unchecked_Find ( Container.Roots (Key_Index'First).all, Keys (Key_Index'First) ); Index : Byte_Index; begin if Item = External_B_Tree.No_Item then -- Insert Unchecked_Add (Container, Keys, Values); else Index := Unchecked_Get_Value (Item); declare Block : Block_Type renames Update (Container.Pool.File, Index).all; Offset : Block_Offset := Get_Offset (Index) + Values_Offs; begin Set_Values (Block, Offset, Values); end; end if; end Replace; procedure Replace ( Container : in out Table; Keys : Keys_Tuple; Values : Values_Tuple; Replaced : out Values_Tuple ) is Lock : Holder (Container.Pool); Item : constant Item_Ptr := Unchecked_Find ( Container.Roots (Key_Index'First).all, Keys (Key_Index'First) ); Index : Byte_Index; begin if Item = External_B_Tree.No_Item then -- Insert Unchecked_Add (Container, Keys, Values); Replaced := (others => 0); else Index := Unchecked_Get_Value (Item); declare Block : Block_Type renames Update (Container.Pool.File, Index).all; Offset : Block_Offset := Get_Offset (Index) + Values_Offs; begin Get_Values (Block, Offset, Replaced); Offset := Get_Offset (Index) + Values_Offs; Set_Values (Block, Offset, Values); end; end if; end Replace; procedure Replace ( Container : in out Table; Index : Key_Index; Key : Byte_Index; Column : Value_Index; Value : Byte_Index ) is Lock : Holder (Container.Pool); Item : constant Byte_Index := Unchecked_Get ( Container.Roots (Index).all, Key ); Block : Block_Type renames Update (Container.Pool.File, Item).all; Offset : constant Block_Offset := ( Get_Offset (Item) + Values_Offs + Value_Index'Pos (Column) * 8 ); begin Put (Block, Offset, Value); end Replace; procedure Replace ( Container : in out Table; Index : Key_Index; Key : Byte_Index; Column : Value_Index; Value : Byte_Index; Replaced : out Byte_Index ) is Lock : Holder (Container.Pool); Item : constant Byte_Index := Unchecked_Get ( Container.Roots (Index).all, Key ); Block : Block_Type renames Update (Container.Pool.File, Item).all; Offset : constant Block_Offset := ( Get_Offset (Item) + Values_Offs + Value_Index'Pos (Column) * 8 ); begin Replaced := Get (Block, Offset); Put (Block, Offset, Value); end Replace; procedure Restore ( Container : in out Table; Reference : Byte_Index ) is begin Set_Root_Address (Container, Reference); end Restore; procedure Set_Root_Address ( Container : in out Table; Root : Byte_Index ) is Lock : Holder (Container.Pool); Block : Block_Type renames Load (Container.Pool.File, Root).all; Offset : Block_Offset := Get_Offset (Root); begin if Container.Address /= 0 then Raise_Exception (Status_Error'Identity, Not_Empty); end if; for Index in Container.Roots'Range loop if Container.Roots (Index).Root_Bucket /= 0 then Raise_Exception (Status_Error'Identity, Not_Empty); end if; end loop; for Index in Container.Roots'Range loop Container.Roots (Index).Root_Bucket := Get (Block, Offset); Offset := Offset + 8; end loop; Container.Address := Root; end Set_Root_Address; procedure Set_Keys ( Block : in out Block_Type; Offset : in out Block_Offset; Keys : Keys_Tuple ) is begin for Index in Keys'Range loop Put (Block, Offset, Keys (Index)); Offset := Offset + 8; end loop; end Set_Keys; procedure Set_Values ( Block : in out Block_Type; Offset : in out Block_Offset; Values : Values_Tuple ) is begin for Index in Values'Range loop Put (Block, Offset, Values (Index)); Offset := Offset + 8; end loop; end Set_Values; procedure Store ( Container : in out Table; Reference : out Byte_Index ) is Lock : Holder (Container.Pool); begin Reference := Get_Root_Address (Container); Container.Address := 0; for Index in Container.Roots'Range loop Container.Roots (Index).Root_Bucket := 0; end loop; end Store; function Sup ( Container : Table; Index : Key_Index; Key : Byte_Index ) return Row_Ptr is Item : constant Item_Ptr := Sup (Container.Roots (Index).all, Key); begin if Item = No_Item then return No_Row; else return (Container.Self, Item.Node, Item.Index); end if; end Sup; procedure Unchecked_Add ( Container : in out Table; Keys : Keys_Tuple; Values : Values_Tuple ) is Pool : Persistent_Pool'Class renames Container.Pool.all; Item : Byte_Index; begin Item := Unchecked_Allocate (Pool, (Values'Length + Keys'Length) * 8); for Index in Keys'Range loop begin Unchecked_Add ( Container.Roots (Index).all, Keys (Index), Item ); exception when Constraint_Error => -- Roll back added keys if Key_Index'Pos (Index) > 0 then for Added in Keys'First..Key_Index'Pred (Index) loop Unchecked_Remove ( Container.Roots (Added).all, Keys (Added) ); end loop; end if; Unchecked_Deallocate (Pool, Item); raise; end; end loop; declare Block : Block_Type renames Update (Pool.File, Item).all; Offset : Block_Offset := Get_Offset (Item); begin Set_Keys (Block, Offset, Keys); Set_Values (Block, Offset, Values); end; end Unchecked_Add; procedure Update ( Row : Row_Ptr; Handler : in out Update_Handler'Class ) is Lock : Holder (Row.Table.Pool); This : constant Byte_Index := Unchecked_Get_Value ( Item_Ptr' ( Tree => Row.Table.Roots (Key_Index'First), Node => Row.Node, Index => Row.Index ) ); Block : Block_Type renames Update (Row.Table.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This); Keys : Keys_Tuple; Values : Values_Tuple; begin Get_Keys (Block, Offset, Keys); Get_Values (Block, Offset, Values); Update (Handler, Keys, Values); Offset := Get_Offset (This) + Values_Offs; Set_Values (Block, Offset, Values); end Update; procedure Update ( Container : in out Table; Index : Key_Index; Key : Byte_Index; Handler : in out Update_Handler'Class ) is Lock : Holder (Container.Pool); This : constant Byte_Index := Unchecked_Get ( Container.Roots (Index).all, Key ); Block : Block_Type renames Update (Container.Pool.File, This).all; Offset : Block_Offset := Get_Offset (This); Keys : Keys_Tuple; Values : Values_Tuple; begin Get_Keys (Block, Offset, Keys); Get_Values (Block, Offset, Values); Update (Handler, Keys, Values); Offset := Get_Offset (This) + Values_Offs; Set_Values (Block, Offset, Values); end Update; end Persistent.Memory_Pools.Streams.External_B_Tree.Generic_Table;