You've already forked gnatcoll-db
mirror of
https://github.com/AdaCore/gnatcoll-db.git
synced 2026-02-12 12:59:31 -08:00
1018 lines
30 KiB
Ada
1018 lines
30 KiB
Ada
------------------------------------------------------------------------------
|
|
-- G N A T C O L L --
|
|
-- --
|
|
-- Copyright (C) 2010-2021, AdaCore --
|
|
-- --
|
|
-- This library is free software; you can redistribute it and/or modify it --
|
|
-- under terms of the GNU General Public License as published by the Free --
|
|
-- Software Foundation; either version 3, 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 MERCHAN- --
|
|
-- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. --
|
|
-- --
|
|
-- As a special exception under Section 7 of GPL version 3, you are granted --
|
|
-- additional permissions described in the GCC Runtime Library Exception, --
|
|
-- version 3.1, as published by the Free Software Foundation. --
|
|
-- --
|
|
-- You should have received a copy of the GNU General Public License and --
|
|
-- a copy of the GCC Runtime Library Exception along with this program; --
|
|
-- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
|
|
-- <http://www.gnu.org/licenses/>. --
|
|
-- --
|
|
------------------------------------------------------------------------------
|
|
|
|
-- ??? Other implementations: no pooling (connection/close each time)
|
|
-- ??? From sqlalchemy, a parameter use_threadlocal to always return the same
|
|
-- connection when one has been checked out in the thread already.
|
|
-- ??? sqlalchemy allows overflows in pools
|
|
-- ??? sqlalchemy provides a SingletonThreadPool where a connection is only
|
|
-- returned in the thread that was used to create it (for sqlite)
|
|
|
|
with Ada.Containers; use Ada.Containers;
|
|
with Ada.Unchecked_Deallocation;
|
|
|
|
with GNATCOLL.SQL.Orm;
|
|
with GNATCOLL.Traces; use GNATCOLL.Traces;
|
|
with GNATCOLL.Utils; use GNATCOLL.Utils;
|
|
|
|
package body GNATCOLL.SQL.Sessions is
|
|
-- Work around issue with the Ada containers: the tampering checks
|
|
-- mean that the container might be corrupted if used from multiple
|
|
-- tasks, even in read-only.
|
|
-- pragma Suppress (Tampering_Check);
|
|
|
|
Me : constant Trace_Handle := Create ("Session", Off);
|
|
Me_Info : constant Trace_Handle := Create ("Session.Info");
|
|
|
|
use Element_Maps, Weak_Element_Maps, Element_Lists, Pointers;
|
|
use type Pointers.Element_Access;
|
|
|
|
Default_Fact : Element_Factory := Null_Factory'Access;
|
|
|
|
procedure Unchecked_Free is new Ada.Unchecked_Deallocation
|
|
(Detached_Element'Class, Detached_Element_Access);
|
|
procedure Unchecked_Free is new Ada.Unchecked_Deallocation
|
|
(User_Data'Class, User_Data_Access);
|
|
|
|
function Is_Dirty (Data : Detached_Data_Access) return Boolean;
|
|
pragma Inline (Is_Dirty);
|
|
-- Return True if Data has been modified in memory, and not synchronized
|
|
-- yet with the database
|
|
|
|
type Null_Element is new Detached_Element with null record;
|
|
overriding procedure Insert_Or_Update
|
|
(Self : in out Null_Element;
|
|
PK_Modified : in out Boolean;
|
|
Dirty : Dirty_Mask) is null;
|
|
overriding procedure Internal_Delete (Self : Null_Element) is null;
|
|
-- A special, always uninitialized element
|
|
|
|
procedure Clear_Cache (Data : in out Session_Data);
|
|
-- Clear the cache, releasing memory as appropriate
|
|
|
|
function Image (K : Element_Key) return String;
|
|
procedure Trace_Debug
|
|
(Me : GNATCOLL.Traces.Trace_Handle;
|
|
K : Weak_Cache;
|
|
Msg : String := "");
|
|
procedure Trace_Debug
|
|
(Me : GNATCOLL.Traces.Trace_Handle;
|
|
E : Detached_Element_Access;
|
|
Msg : String := "");
|
|
-- Print information on C
|
|
|
|
procedure Add_To_Cache (Self : Session_Type; E : Detached_Element'Class);
|
|
-- Add the element into the cache. We want the element to outlive the
|
|
-- session (so that we can find out all changes when committing the
|
|
-- session), so we store a real reference.
|
|
|
|
-----------------
|
|
-- Ref_Or_Weak --
|
|
-----------------
|
|
|
|
function Get_Data (Self : Weak_Cache) return Detached_Data_Access;
|
|
function Get_Data
|
|
(Self : Detached_Element_Access) return Detached_Data_Access;
|
|
pragma Inline (Get_Data);
|
|
-- Returned the element data stored in Self
|
|
|
|
--------------
|
|
-- Get_Data --
|
|
--------------
|
|
|
|
function Get_Data (Self : Weak_Cache) return Detached_Data_Access is
|
|
R : Pointers.Ref;
|
|
begin
|
|
R.Set (Self.Ref);
|
|
return Detached_Data_Access (R.Unchecked_Get);
|
|
-- ??? That's bad, the data could be freed while we return it
|
|
end Get_Data;
|
|
|
|
--------------
|
|
-- Get_Data --
|
|
--------------
|
|
|
|
function Get_Data
|
|
(Self : Detached_Element_Access) return Detached_Data_Access is
|
|
begin
|
|
return Detached_Data_Access (Self.Unchecked_Get);
|
|
end Get_Data;
|
|
|
|
------------------
|
|
-- Impl_Factory --
|
|
------------------
|
|
|
|
function Impl_Factory (Data : Pool_Data) return Session_Data is
|
|
DB : constant Database_Connection := Data.Descr.Build_Connection;
|
|
begin
|
|
Assert
|
|
(Me, DB /= null,
|
|
"Could not connect to database. Wrong dbtype set in settings ?");
|
|
|
|
return (DB => DB,
|
|
Pool => Data.Pool,
|
|
Wcache => Weak_Element_Maps.Empty_Map,
|
|
Cache => Element_Maps.Empty_Map,
|
|
Modified_Elements => Element_Lists.Empty_Vector,
|
|
User => null,
|
|
Store_Unmodified => Data.Config_Store_Unmodified,
|
|
Weak_Cache => Data.Config_Weak_Cache,
|
|
Persist_Cascade => Data.Config_Persist_Cascade,
|
|
Flush_Before_Query => Data.Config_Flush_Before_Query,
|
|
Factory => Default_Fact);
|
|
end Impl_Factory;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free (Data : in out Pool_Data) is
|
|
begin
|
|
Free (Data.Descr);
|
|
|
|
if Data.Config_Default_User_Data /= null then
|
|
Free (Data.Config_Default_User_Data.all);
|
|
Unchecked_Free (Data.Config_Default_User_Data);
|
|
end if;
|
|
end Free;
|
|
|
|
-----------------
|
|
-- Clear_Cache --
|
|
-----------------
|
|
|
|
procedure Clear_Cache (Data : in out Session_Data) is
|
|
WC : Weak_Element_Maps.Cursor;
|
|
WRef : Weak_Cache;
|
|
E : Detached_Element_Access;
|
|
C : Element_Maps.Cursor;
|
|
D : Detached_Data_Access;
|
|
begin
|
|
if Data.Weak_Cache then
|
|
WC := Data.Wcache.First;
|
|
while Has_Element (WC) loop
|
|
WRef := Element (WC);
|
|
|
|
if Active (Me) then
|
|
Trace_Debug (Me, WRef, "Removed from cache: ");
|
|
end if;
|
|
|
|
-- The element no longer belongs to the session (it might actually
|
|
-- be freed by Unchecked_Free, but we do not know that for sure)
|
|
D := Get_Data (WRef);
|
|
if D /= null then
|
|
D.Session := Null_Weak_Session;
|
|
end if;
|
|
|
|
Next (WC);
|
|
end loop;
|
|
Data.Wcache.Clear;
|
|
else
|
|
C := Data.Cache.First;
|
|
while Has_Element (C) loop
|
|
E := Element (C);
|
|
|
|
if Active (Me) then
|
|
Trace_Debug (Me, E, "Removed from cache: ");
|
|
end if;
|
|
|
|
D := Get_Data (E);
|
|
if D /= null then
|
|
D.Session := Null_Weak_Session;
|
|
end if;
|
|
Unchecked_Free (E);
|
|
|
|
Next (C);
|
|
end loop;
|
|
Data.Cache.Clear;
|
|
end if;
|
|
|
|
Data.Modified_Elements.Clear;
|
|
end Clear_Cache;
|
|
|
|
---------------------
|
|
-- Impl_On_Release --
|
|
---------------------
|
|
|
|
procedure Impl_On_Release (Data : in out Session_Data) is
|
|
begin
|
|
Increase_Indent (Me, "Releasing session in pool "
|
|
& Session_Pool'Image (Data.Pool));
|
|
Clear_Cache (Data);
|
|
|
|
if Data.DB.Automatic_Transactions then
|
|
Rollback (Data.DB, ""); -- Release any pending transaction
|
|
end if;
|
|
|
|
if Data.User /= null then
|
|
Trace (Me, "Freeing session data");
|
|
Free (Data.User.all);
|
|
Unchecked_Free (Data.User);
|
|
end if;
|
|
|
|
Decrease_Indent (Me, "Done releasing session");
|
|
end Impl_On_Release;
|
|
|
|
---------------
|
|
-- Impl_Free --
|
|
---------------
|
|
|
|
procedure Impl_Free (Data : in out Session_Data) is
|
|
begin
|
|
if Active (Me) then
|
|
Trace (Me, "Freeing a session and its cache, closing SQL connection"
|
|
& " in pool " & Session_Pool'Image (Data.Pool));
|
|
end if;
|
|
Clear_Cache (Data); -- Should have been done in Impl_On_Release
|
|
|
|
Data.Factory := Default_Fact;
|
|
|
|
Free (Data.DB); -- Close connection
|
|
end Impl_Free;
|
|
|
|
---------------------
|
|
-- Get_New_Session --
|
|
---------------------
|
|
|
|
function Get_New_Session
|
|
(Pool : Session_Pool := Default_Pool) return Session_Type
|
|
is
|
|
Self : Session_Type;
|
|
begin
|
|
if Active (Me) then
|
|
Trace (Me, "Getting new session from pool "
|
|
& Session_Pool'Image (Pool));
|
|
end if;
|
|
Impl.Get (Self, Set => Pool);
|
|
Reset_Connection (Self.DB);
|
|
return Self;
|
|
end Get_New_Session;
|
|
|
|
-----------
|
|
-- Setup --
|
|
-----------
|
|
|
|
procedure Setup
|
|
(Descr : GNATCOLL.SQL.Exec.Database_Description;
|
|
Max_Sessions : Positive;
|
|
Default_User_Data : User_Data'Class := No_User_Data;
|
|
Store_Unmodified : Boolean := False;
|
|
Weak_Cache : Boolean := True;
|
|
Flush_Before_Query : Boolean := True;
|
|
Persist_Cascade : Boolean := True;
|
|
Pool : Session_Pool := Default_Pool)
|
|
is
|
|
begin
|
|
Impl.Set_Factory
|
|
(Pool_Data'
|
|
(Descr => Descr,
|
|
Pool => Pool,
|
|
Config_Store_Unmodified => Store_Unmodified,
|
|
Config_Weak_Cache => Weak_Cache,
|
|
Config_Flush_Before_Query => Flush_Before_Query,
|
|
Config_Default_User_Data =>
|
|
new User_Data'Class'(Default_User_Data),
|
|
Config_Persist_Cascade => Persist_Cascade),
|
|
Max_Sessions, Set => Pool);
|
|
end Setup;
|
|
|
|
-------------------------
|
|
-- Set_Default_Factory --
|
|
-------------------------
|
|
|
|
procedure Set_Default_Factory (Factory : Element_Factory) is
|
|
begin
|
|
Default_Fact := Factory;
|
|
end Set_Default_Factory;
|
|
|
|
-----------------
|
|
-- Set_Factory --
|
|
-----------------
|
|
|
|
procedure Set_Factory
|
|
(Self : in out Session_Type'Class;
|
|
Factory : Element_Factory) is
|
|
begin
|
|
Self.Element.Factory := Factory;
|
|
end Set_Factory;
|
|
|
|
------------------
|
|
-- Null_Factory --
|
|
------------------
|
|
|
|
function Null_Factory
|
|
(From : Base_Element'Class;
|
|
Default : Detached_Element'Class) return Detached_Element'Class
|
|
is
|
|
pragma Unreferenced (From);
|
|
begin
|
|
return Default;
|
|
end Null_Factory;
|
|
|
|
-------------
|
|
-- Factory --
|
|
-------------
|
|
|
|
function Factory
|
|
(Self : Session_Type'Class;
|
|
From : Base_Element'Class;
|
|
Default : Detached_Element'Class) return Detached_Element'Class is
|
|
begin
|
|
return Self.Element.Factory (From, Default);
|
|
end Factory;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
overriding procedure Free (Self : in out Detached_Data) is
|
|
WC : Weak_Element_Maps.Cursor;
|
|
Session : constant Session_Type := Get (Self.Session);
|
|
D : access Session_Data;
|
|
K : Element_Key;
|
|
Template : Detached_Element_Access;
|
|
begin
|
|
-- This procedure is only called for elements that have a weak-ref (or
|
|
-- no ref at all) in a session. If the session is holding a real
|
|
-- reference, Self cannot be freed anyway.
|
|
-- The goal here is to save memory by removing the element's weak ref
|
|
-- from the session cache. This is not mandatory, since the weak ref
|
|
-- remains usable, but ensures memory usage does not grow up too much.
|
|
|
|
-- This will actually free Self, and properly reset all weakref to it.
|
|
-- At this point, the weak references in the cache that are no longer
|
|
-- pointing to anything can simply be removed.
|
|
|
|
if Session /= No_Session then
|
|
D := Session.Element;
|
|
if D.Weak_Cache then
|
|
K := Key (Detached_Data'Class (Self));
|
|
WC := D.Wcache.Find (K);
|
|
if Has_Element (WC) then
|
|
if Active (Me) then
|
|
Trace (Me, "Removing from cache: freed weakref "
|
|
& Image (K));
|
|
end if;
|
|
|
|
Template := Element (WC).Template;
|
|
Unchecked_Free (Template);
|
|
D.Wcache.Delete (WC);
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end Free;
|
|
|
|
-----------
|
|
-- Image --
|
|
-----------
|
|
|
|
function Image (K : Element_Key) return String is
|
|
begin
|
|
return "<" & Image (K.Table, Min_Width => 1)
|
|
& ',' & Image (K.Key, Min_Width => 1) & ">";
|
|
end Image;
|
|
|
|
-----------------
|
|
-- Trace_Debug --
|
|
-----------------
|
|
|
|
procedure Trace_Debug
|
|
(Me : GNATCOLL.Traces.Trace_Handle;
|
|
K : Weak_Cache;
|
|
Msg : String := "")
|
|
is
|
|
Data : constant Detached_Data_Access := Get_Data (K);
|
|
begin
|
|
if Was_Freed (K.Ref) then
|
|
Trace (Me, Msg & "weakref to freed '" & Image (Key (Data.all)) & "'");
|
|
else
|
|
Trace (Me, Msg & "weakref to '" & Image (Key (Data.all)) & "'");
|
|
end if;
|
|
end Trace_Debug;
|
|
|
|
-----------------
|
|
-- Trace_Debug --
|
|
-----------------
|
|
|
|
procedure Trace_Debug
|
|
(Me : GNATCOLL.Traces.Trace_Handle;
|
|
E : Detached_Element_Access;
|
|
Msg : String := "")
|
|
is
|
|
Data : constant Detached_Data_Access := Get_Data (E);
|
|
Count : Integer;
|
|
begin
|
|
Count := Get_Refcount (E.all);
|
|
if not Active (Me) and then Count /= 1 then
|
|
-- Always show when an element has remaining references, since it
|
|
-- helps debugging memory issues.
|
|
Trace (Me_Info, Msg & " (remaining refs to '"
|
|
& Image (Key (Data.all))
|
|
& "' refcount=" & Count'Img & ")");
|
|
end if;
|
|
|
|
if Data = null then
|
|
Trace (Me, Msg & "ref to <null> refcount=" & Count'Img);
|
|
elsif Is_Dirty (Data) then
|
|
Trace (Me, Msg & "ref to modified '"
|
|
& Image (Key (Data.all))
|
|
& "' refcount=" & Count'Img);
|
|
else
|
|
Trace (Me, Msg & "ref to unmodified '"
|
|
& Image (Key (Data.all))
|
|
& "' refcount=" & Count'Img);
|
|
end if;
|
|
end Trace_Debug;
|
|
|
|
-----------------
|
|
-- Trace_Debug --
|
|
-----------------
|
|
|
|
procedure Trace_Debug
|
|
(Me : GNATCOLL.Traces.Trace_Handle;
|
|
Session : Session_Type;
|
|
Msg : String := "")
|
|
is
|
|
C : Element_Maps.Cursor;
|
|
WC : Weak_Element_Maps.Cursor;
|
|
begin
|
|
Increase_Indent (Me, Msg);
|
|
Trace (Me, "Refcount=" & Get_Refcount (Session)'Img);
|
|
|
|
if Session.Element.Weak_Cache then
|
|
WC := Session.Element.Wcache.First;
|
|
while Has_Element (WC) loop
|
|
Trace_Debug (Me, Element (WC), "");
|
|
Next (WC);
|
|
end loop;
|
|
|
|
else
|
|
C := Session.Element.Cache.First;
|
|
while Has_Element (C) loop
|
|
Trace_Debug (Me, Element (C), "");
|
|
Next (C);
|
|
end loop;
|
|
end if;
|
|
|
|
Decrease_Indent (Me, "Done " & Msg);
|
|
|
|
exception
|
|
when E : others =>
|
|
Decrease_Indent (Me, "Done " & Msg & " Unexpected exception");
|
|
Trace (Me, E);
|
|
end Trace_Debug;
|
|
|
|
---------
|
|
-- "=" --
|
|
---------
|
|
|
|
function "=" (W1, W2 : Weak_Cache) return Boolean is
|
|
begin
|
|
return W1.Ref = W2.Ref;
|
|
end "=";
|
|
|
|
----------
|
|
-- Hash --
|
|
----------
|
|
|
|
function Hash (Key : Element_Key) return Ada.Containers.Hash_Type is
|
|
begin
|
|
if Key.Key = No_Primary_Key then
|
|
return Ada.Containers.Hash_Type (Key.Table);
|
|
else
|
|
return Ada.Containers.Hash_Type
|
|
(Long_Long_Integer (Key.Table + Key.Key)
|
|
mod Long_Long_Integer (Hash_Type'Last));
|
|
end if;
|
|
end Hash;
|
|
|
|
----------------
|
|
-- From_Cache --
|
|
----------------
|
|
|
|
function From_Cache
|
|
(Self : Session_Type;
|
|
Key : Element_Key;
|
|
If_Not_Found : Detached_Element'Class) return Detached_Element'Class
|
|
is
|
|
D : constant access Session_Data := Self.Element;
|
|
C : Element_Maps.Cursor;
|
|
WC : Weak_Element_Maps.Cursor;
|
|
Wref : Weak_Cache;
|
|
begin
|
|
if D.Weak_Cache then
|
|
-- Find will call Equivalent to find a matching element (after using
|
|
-- the Hash to find the bucket).
|
|
WC := D.Wcache.Find (Key);
|
|
if Has_Element (WC) then
|
|
Wref := Weak_Element_Maps.Element (WC);
|
|
declare
|
|
R : Detached_Element'Class := Wref.Template.all;
|
|
begin
|
|
R.Set (Wref.Ref);
|
|
return R;
|
|
end;
|
|
end if;
|
|
else
|
|
C := D.Cache.Find (Key);
|
|
if Has_Element (C) then
|
|
return Element_Maps.Element (C).all;
|
|
end if;
|
|
end if;
|
|
return If_Not_Found;
|
|
end From_Cache;
|
|
|
|
------------------
|
|
-- Set_Modified --
|
|
------------------
|
|
|
|
procedure Set_Modified
|
|
(Self : Detached_Element; Field : Natural)
|
|
is
|
|
D : constant Detached_Data_Access :=
|
|
Detached_Data_Access (Self.Unchecked_Get);
|
|
Session : Session_Type;
|
|
Was_Dirty : constant Boolean := Is_Dirty (D);
|
|
begin
|
|
if Active (Me) and then not D.Dirty (Field) then
|
|
Trace (Me, "Set_Modified, Field=" & Field'Img
|
|
& " key=" & Image (Key (D.all))
|
|
& " Was_Dirty=" & Was_Dirty'Img);
|
|
end if;
|
|
|
|
D.Dirty (Field) := True;
|
|
|
|
if not Was_Dirty then
|
|
-- Add the element to the list of modified elements. This also
|
|
-- ensures we have a real reference to it so that it isn't finalized
|
|
-- before we have flushed it to the db.
|
|
|
|
Session := Get (D.Session);
|
|
if Session /= No_Session then
|
|
Session.Element.Modified_Elements.Append (Self);
|
|
if Active (Me) then
|
|
Trace (Me, " Set_Modified: added to session's modified list");
|
|
end if;
|
|
end if;
|
|
|
|
-- We do not need to add the element to the cache:
|
|
-- either it was retrieved through the session and is already in the
|
|
-- cache, or it is a new element which we would not be able to cache
|
|
-- anyway.
|
|
end if;
|
|
end Set_Modified;
|
|
|
|
--------
|
|
-- DB --
|
|
--------
|
|
|
|
function DB (Self : Session_Type) return Database_Connection is
|
|
begin
|
|
return Self.Element.DB;
|
|
end DB;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free is
|
|
begin
|
|
Impl.Free;
|
|
end Free;
|
|
|
|
--------------
|
|
-- Get_Weak --
|
|
--------------
|
|
|
|
function Get_Weak (Self : Session_Type) return Weak_Session is
|
|
W : Impl.Weak_Resource;
|
|
begin
|
|
W := Impl.Get_Weak (Impl.Resource (Self));
|
|
return Weak_Session'(Ref => W);
|
|
end Get_Weak;
|
|
|
|
---------
|
|
-- Get --
|
|
---------
|
|
|
|
function Get (Self : Weak_Session) return Session_Type is
|
|
Result : Impl.Resource;
|
|
begin
|
|
if Impl.Was_Freed (Self.Ref) then
|
|
return No_Session;
|
|
else
|
|
Impl.Get (Self.Ref, Result);
|
|
return (Result with null record);
|
|
end if;
|
|
end Get;
|
|
|
|
-------------
|
|
-- Persist --
|
|
-------------
|
|
|
|
procedure Persist
|
|
(Self : Session_Type; Element : Detached_Element'Class)
|
|
is
|
|
D : constant Detached_Data_Access :=
|
|
Detached_Data_Access (Element.Unchecked_Get);
|
|
begin
|
|
if D = null then
|
|
return;
|
|
end if;
|
|
|
|
-- Store a weak-reference to the session in the element, so that it can
|
|
-- be used to query further attributes, or register changes
|
|
-- The Element can outlive the session, so we store a weak-reference.
|
|
|
|
if Get (D.Session) /= No_Session then
|
|
if Get (D.Session) /= Self then
|
|
raise Already_Persistent
|
|
with "Element already belongs to another session";
|
|
end if;
|
|
|
|
-- Element is already in the session, but might not be in the cache
|
|
-- if it wasn't modified before and Config_Store_Unmodified is False.
|
|
-- If it is in the session, and it is dirty, it has already been
|
|
-- added to the list of modified elements.
|
|
|
|
else
|
|
D.Session := Get_Weak (Self);
|
|
|
|
if Is_Dirty (D) then
|
|
if Active (Me) then
|
|
Trace (Me, "Persisting a modified element: "
|
|
& Image (Key (D.all)));
|
|
end if;
|
|
Self.Element.Modified_Elements.Append (Element);
|
|
elsif not Self.Element.Store_Unmodified then
|
|
return;
|
|
end if;
|
|
end if;
|
|
|
|
Add_To_Cache (Self, Element);
|
|
end Persist;
|
|
|
|
------------------
|
|
-- Add_To_Cache --
|
|
------------------
|
|
|
|
procedure Add_To_Cache (Self : Session_Type; E : Detached_Element'Class) is
|
|
K : constant Element_Key :=
|
|
Key (Detached_Data_Access (E.Unchecked_Get).all);
|
|
T : Detached_Element_Access;
|
|
Inserted : Boolean;
|
|
C : Element_Maps.Cursor;
|
|
WC : Weak_Element_Maps.Cursor;
|
|
begin
|
|
if K.Key = No_Primary_Key then
|
|
-- Can't add an element whose primary key is unknown, since we do not
|
|
-- have a unique key for it. But the element has likely been added
|
|
-- (via Persist) to the list of Modified_Elements, and when the
|
|
-- element is inserted into the database it will also be added to
|
|
-- the database automatically).
|
|
|
|
return;
|
|
end if;
|
|
|
|
if Self.Element.Weak_Cache then
|
|
-- We only want the tag, not a reference
|
|
T := new Detached_Element'Class'(E);
|
|
T.Set (Pointers.Null_Weak_Ref);
|
|
|
|
Self.Element.Wcache.Insert
|
|
(Key => K,
|
|
New_Item => (Ref => E.Weak,
|
|
Template => T),
|
|
Position => WC,
|
|
Inserted => Inserted);
|
|
else
|
|
T := new Detached_Element'Class'(E);
|
|
Self.Element.Cache.Insert
|
|
(Key => K, New_Item => T,
|
|
Position => C,
|
|
Inserted => Inserted);
|
|
end if;
|
|
|
|
if Inserted then
|
|
if Active (Me) then
|
|
Trace (Me, "Add to cache: " & Image (K));
|
|
end if;
|
|
On_Persist (E);
|
|
else
|
|
Unchecked_Free (T);
|
|
end if;
|
|
end Add_To_Cache;
|
|
|
|
--------------
|
|
-- Is_Dirty --
|
|
--------------
|
|
|
|
function Is_Dirty (Data : Detached_Data_Access) return Boolean is
|
|
begin
|
|
for D in Data.Dirty'Range loop
|
|
if Data.Dirty (D) then
|
|
return True;
|
|
end if;
|
|
end loop;
|
|
return False;
|
|
end Is_Dirty;
|
|
|
|
-----------------
|
|
-- Cache_Count --
|
|
-----------------
|
|
|
|
procedure Cache_Count
|
|
(Self : Session_Type;
|
|
Refs : out Natural;
|
|
Weakref : out Natural) is
|
|
begin
|
|
if Self.Element.Weak_Cache then
|
|
Weakref := Integer (Length (Self.Element.Wcache));
|
|
Refs := 0;
|
|
else
|
|
Weakref := 0;
|
|
Refs := Integer (Length (Self.Element.Cache));
|
|
end if;
|
|
end Cache_Count;
|
|
|
|
---------------------
|
|
-- Persist_Cascade --
|
|
---------------------
|
|
|
|
function Persist_Cascade (Self : Session_Type) return Boolean is
|
|
begin
|
|
return Self /= No_Session
|
|
and then Self.Element.Persist_Cascade;
|
|
end Persist_Cascade;
|
|
|
|
----------------------
|
|
-- Insert_Or_Update --
|
|
----------------------
|
|
|
|
procedure Insert_Or_Update
|
|
(Self : Session_Type;
|
|
Element : in out Detached_Element'Class)
|
|
is
|
|
D : constant Detached_Data_Access :=
|
|
Detached_Data_Access (Element.Unchecked_Get);
|
|
PK_Modified : Boolean := False;
|
|
begin
|
|
if D = null or else not Is_Dirty (D) then
|
|
-- Nothing to do
|
|
return;
|
|
end if;
|
|
|
|
if Active (Me) then
|
|
Trace_Debug (Me, Element'Unchecked_Access, "Insert_Or_Update: ");
|
|
end if;
|
|
|
|
declare
|
|
Dirty : constant Dirty_Mask := D.Dirty;
|
|
Old_K : constant Element_Key := Key (D.all);
|
|
begin
|
|
|
|
-- Reset the dirty mask, to prevent infinite recursion when an
|
|
-- element depends (possibly indirectly) on itself
|
|
|
|
D.Dirty := (others => False);
|
|
|
|
if Dirty (Dirty_Mask_Deleted) then
|
|
Internal_Delete (Element);
|
|
|
|
-- Remove the element from the cache. In theory at least, the
|
|
-- element no longer exists
|
|
--
|
|
-- ??? In theory at least, we should only remove elements last,
|
|
-- after all other changes have been done (so that any FK
|
|
-- reference to them has been updated). Likewise, we should add
|
|
-- elements first).
|
|
PK_Modified := True;
|
|
|
|
else
|
|
Insert_Or_Update (Element, PK_Modified, Dirty);
|
|
|
|
if PK_Modified then
|
|
if Active (Me) then
|
|
Trace (Me, "PK has changed, adding new value to cache");
|
|
end if;
|
|
|
|
Add_To_Cache (Self, Element); -- Insert with new key
|
|
end if;
|
|
end if;
|
|
|
|
if PK_Modified and then Old_K.Key /= No_Primary_Key then
|
|
if Active (Me) then
|
|
Trace (Me, "PK has changed, removing old from cache: "
|
|
& Image (Old_K));
|
|
end if;
|
|
|
|
-- Remove the old element from the cache
|
|
if Self.Element.Weak_Cache then
|
|
Self.Element.Wcache.Exclude (Old_K);
|
|
else
|
|
Self.Element.Cache.Exclude (Old_K);
|
|
end if;
|
|
end if;
|
|
end;
|
|
end Insert_Or_Update;
|
|
|
|
------------
|
|
-- Delete --
|
|
------------
|
|
|
|
procedure Delete
|
|
(Self : Session_Type; Element : Detached_Element'Class)
|
|
is
|
|
pragma Unreferenced (Self);
|
|
begin
|
|
Element.Set_Modified (Dirty_Mask_Deleted);
|
|
end Delete;
|
|
|
|
-----------
|
|
-- Flush --
|
|
-----------
|
|
|
|
procedure Flush (Self : Session_Type) is
|
|
procedure Process (E : in out Detached_Element'Class);
|
|
procedure Process (E : in out Detached_Element'Class) is
|
|
begin
|
|
Insert_Or_Update (Self, E);
|
|
end Process;
|
|
|
|
Data : constant access Session_Data := Self.Element;
|
|
C : Element_Lists.Cursor := Data.Modified_Elements.First;
|
|
begin
|
|
if Has_Element (C) then
|
|
Increase_Indent (Me, "Flushing session");
|
|
while Has_Element (C) loop
|
|
Update_Element (Data.Modified_Elements, C, Process'Access);
|
|
Next (C);
|
|
end loop;
|
|
|
|
Data.Modified_Elements.Clear;
|
|
Decrease_Indent (Me, "Done flushing session");
|
|
end if;
|
|
|
|
-- ??? The only delicate area is when you have default values set
|
|
-- by the database, in which case we might not have the value in
|
|
-- memory and will not fetch it next time.
|
|
|
|
exception
|
|
when GNATCOLL.SQL.Orm.Self_Referencing =>
|
|
Decrease_Indent (Me, "Got self-referencing");
|
|
raise;
|
|
|
|
when E : others =>
|
|
Trace (Me, E, "while flushing session");
|
|
Decrease_Indent (Me);
|
|
end Flush;
|
|
|
|
-----------------------
|
|
-- Begin_Transaction --
|
|
-----------------------
|
|
|
|
procedure Begin_Transaction (Self : Session_Type) is
|
|
begin
|
|
if not In_Transaction (Self.DB) then
|
|
Execute (Self.DB, SQL_Begin);
|
|
end if;
|
|
end Begin_Transaction;
|
|
|
|
--------------------
|
|
-- In_Transaction --
|
|
--------------------
|
|
|
|
function In_Transaction (Self : Session_Type) return Boolean is
|
|
begin
|
|
return In_Transaction (Self.DB);
|
|
end In_Transaction;
|
|
|
|
------------
|
|
-- Commit --
|
|
------------
|
|
|
|
procedure Commit (Self : Session_Type) is
|
|
begin
|
|
Flush (Self);
|
|
if In_Transaction (Self.DB) then
|
|
Execute (Self.DB, SQL_Commit);
|
|
end if;
|
|
end Commit;
|
|
|
|
--------------
|
|
-- Rollback --
|
|
--------------
|
|
|
|
procedure Rollback (Self : Session_Type) is
|
|
begin
|
|
Trace (Me, "Rollback session");
|
|
if In_Transaction (Self.DB) then
|
|
Execute (Self.DB, SQL_Rollback);
|
|
end if;
|
|
|
|
Clear_Cache (Self.Element.all);
|
|
end Rollback;
|
|
|
|
-------------
|
|
-- Session --
|
|
-------------
|
|
|
|
function Session (Self : Detached_Element'Class) return Session_Type is
|
|
begin
|
|
return Get (Detached_Data_Access (Self.Unchecked_Get).Session);
|
|
end Session;
|
|
|
|
----------------------
|
|
-- Get_Weak_Session --
|
|
----------------------
|
|
|
|
function Get_Weak_Session
|
|
(Self : Detached_Element'Class) return Weak_Session is
|
|
begin
|
|
return Detached_Data (Self.Get.Element.all).Session;
|
|
end Get_Weak_Session;
|
|
|
|
------------------------
|
|
-- Flush_Before_Query --
|
|
------------------------
|
|
|
|
function Flush_Before_Query (Self : Session_Type) return Boolean is
|
|
begin
|
|
return Self.Element.Flush_Before_Query;
|
|
end Flush_Before_Query;
|
|
|
|
---------------------------
|
|
-- Set_Default_User_Data --
|
|
---------------------------
|
|
|
|
procedure Set_Default_User_Data
|
|
(Default_User_Data : User_Data'Class := No_User_Data;
|
|
Pool : Session_Pool := Default_Pool)
|
|
is
|
|
P : constant access Pool_Data := Impl.Get_Factory_Param (Pool);
|
|
begin
|
|
Free (P.Config_Default_User_Data.all);
|
|
Unchecked_Free (P.Config_Default_User_Data);
|
|
P.Config_Default_User_Data := new User_Data'Class'(Default_User_Data);
|
|
end Set_Default_User_Data;
|
|
|
|
-------------------
|
|
-- Get_User_Data --
|
|
-------------------
|
|
|
|
function Get_User_Data
|
|
(Self : Session_Type; Pool : Session_Pool := Default_Pool)
|
|
return access User_Data'Class
|
|
is
|
|
D : constant access Session_Data := Self.Element;
|
|
P : access Pool_Data;
|
|
begin
|
|
if D.User = null then
|
|
P := Impl.Get_Factory_Param (Pool);
|
|
if P.Config_Default_User_Data /= null then
|
|
D.User := new User_Data'Class'(P.Config_Default_User_Data.all);
|
|
end if;
|
|
end if;
|
|
|
|
return D.User;
|
|
end Get_User_Data;
|
|
|
|
------------
|
|
-- Delete --
|
|
------------
|
|
|
|
procedure Delete (Element : Detached_Element) is
|
|
begin
|
|
Element.Session.Delete (Element);
|
|
end Delete;
|
|
|
|
-------------------
|
|
-- Free_Dispatch --
|
|
-------------------
|
|
|
|
procedure Free_Dispatch (Self : in out Base_Detached_Data'Class) is
|
|
begin
|
|
Free (Self);
|
|
end Free_Dispatch;
|
|
|
|
end GNATCOLL.SQL.Sessions;
|