Files
gnatcoll-db/sql/gnatcoll-sql-exec-tasking.adb
Vasiliy Fofanov c067251840 Fix various typos and headers, bump copyright.
Motivated by github PR #14. no-tn-check

Change-Id: I866431c55fe0937f348d08cd8a188eb40fc8f061
2020-06-12 19:29:25 +02:00

438 lines
14 KiB
Ada

------------------------------------------------------------------------------
-- G N A T C O L L --
-- --
-- Copyright (C) 2005-2020, 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/>. --
-- --
------------------------------------------------------------------------------
pragma Ada_2012;
with Ada.Containers.Hashed_Maps;
with Ada.Containers.Indefinite_Vectors;
with Ada.Finalization;
with Ada.Task_Attributes;
with Ada.Task_Identification; use Ada.Task_Identification;
with GNATCOLL.Strings; use GNATCOLL.Strings;
with GNATCOLL.Traces; use GNATCOLL.Traces;
with GNATCOLL.Refcount; use GNATCOLL.Refcount;
with GNATCOLL.SQL.Exec_Private; use GNATCOLL.SQL.Exec_Private;
with Interfaces.C.Strings; use Interfaces.C.Strings;
package body GNATCOLL.SQL.Exec.Tasking is
use type Ada.Containers.Count_Type;
Me_Error : constant Trace_Handle := Create ("SQL.ERROR", On);
package DB_Attributes is new Ada.Task_Attributes
(Database_Connection, null);
-------------------------
-- Get_Task_Connection --
-------------------------
function Get_Task_Connection
(Description : Database_Description;
Username : String := "")
return Database_Connection
is
Connection : Database_Connection;
begin
Connection := DB_Attributes.Value;
if Connection = null then
Connection := Description.Build_Connection;
if Connection /= null then
DB_Attributes.Set_Value (Connection);
else
Trace
(Me_Error, "Could not create connection object for database");
end if;
else
Reset_Connection (Connection, Username);
end if;
return Connection;
end Get_Task_Connection;
---------------------------------------
-- Task safe cursors implementation --
---------------------------------------
type Field_Value is record
Element : XString;
Empty : Boolean := False;
end record;
type Record_Type is array (Field_Index range <>) of Field_Value;
package Data_Set_Vectors is
new Ada.Containers.Indefinite_Vectors (Natural, Record_Type);
-- Zero index is for field names
package String_Indexes is new Ada.Containers.Hashed_Maps
(XString, Positive, Hash, Equivalent_Keys => "=");
type Data_Set is record
Table : Data_Set_Vectors.Vector;
Index : String_Indexes.Map;
Error : XString;
Status : XString;
TID : Task_Id; -- Keep the Task ID where the cursor is filled
Success : Boolean;
end record;
package Data_Set_Pointers is new Shared_Pointers (Data_Set);
type Task_Cursor is new DBMS_Direct_Cursor with record
Position : Natural := 1;
Data : Data_Set_Pointers.Ref;
end record;
overriding function Is_Success (Self : Task_Cursor) return Boolean
is (Self.Data.Get.Success);
overriding function Is_Null
(Self : Task_Cursor; Field : Field_Index) return Boolean
is (Self.Data.Get.Table (Self.Position) (Field).Empty);
overriding function Error_Msg (Self : Task_Cursor) return String
is (To_String (Self.Data.Get.Error));
overriding function Status (Self : Task_Cursor) return String
is (To_String (Self.Data.Get.Status));
overriding function Has_Row (Self : Task_Cursor) return Boolean
is (Self.Position <= Self.Data.Get.Table.Last_Index);
overriding function Processed_Rows (Self : Task_Cursor) return Natural
is (Self.Data.Get.Table.Last_Index);
overriding function Current (Self : Task_Cursor) return Positive
is (Self.Position);
overriding function Field_Name
(Self : Task_Cursor; Field : Field_Index) return String
is (To_String (Self.Data.Get.Table (0) (Field).Element));
overriding function Field_Count (Self : Task_Cursor) return Field_Index
is (Self.Data.Get.Table (0).Element'Length);
overriding procedure Next (Self : in out Task_Cursor);
overriding function C_Value
(Self : Task_Cursor; Field : Field_Index) return chars_ptr;
overriding function Last_Id
(Self : Task_Cursor;
Connection : access Database_Connection_Record'Class;
Field : SQL_Field_Integer) return Integer;
overriding procedure First (Self : in out Task_Cursor);
overriding procedure Last (Self : in out Task_Cursor);
overriding procedure Absolute (Self : in out Task_Cursor; Row : Positive);
overriding procedure Relative (Self : in out Task_Cursor; Step : Integer);
overriding function Value
(Self : Task_Cursor; Field : Field_Index) return String;
overriding function XString_Value
(Self : Task_Cursor; Field : Field_Index) return XString;
overriding function Boolean_Value
(Self : Task_Cursor; Field : Field_Index) return Boolean;
type Task_Cursor_Access is access all Task_Cursor;
----------
-- Next --
----------
overriding procedure Next (Self : in out Task_Cursor) is
begin
Self.Position := Self.Position + 1;
end Next;
-------------
-- C_Value --
-------------
overriding function C_Value
(Self : Task_Cursor; Field : Field_Index) return chars_ptr
is
pragma Unreferenced (Self, Field);
begin
raise Program_Error with
"Should not be used, because all data in Ada strings already";
return Null_Ptr;
end C_Value;
-------------------
-- Boolean_Value --
-------------------
overriding function Boolean_Value
(Self : Task_Cursor; Field : Field_Index) return Boolean
is
Value : constant String := Self.Value (Field);
begin
return Value'Length > 0
and then (Value (Value'First) = 't'
or else Value (Value'First) = 'T'
or else Value (Value'First) in '1' .. '9');
end Boolean_Value;
-------------
-- Last_Id --
-------------
overriding function Last_Id
(Self : Task_Cursor;
Connection : access Database_Connection_Record'Class;
Field : SQL_Field_Integer) return Integer
is
pragma Unreferenced (Self, Field);
begin
raise Program_Error with
"Task safe cursors is not supported for insert statements";
return 0;
end Last_Id;
-----------
-- First --
-----------
overriding procedure First (Self : in out Task_Cursor) is
begin
Self.Position := 1;
end First;
----------
-- Last --
----------
overriding procedure Last (Self : in out Task_Cursor) is
begin
Self.Position := Self.Data.Get.Table.Last_Index;
end Last;
--------------
-- Absolute --
--------------
overriding procedure Absolute (Self : in out Task_Cursor; Row : Positive) is
begin
Self.Position := Row;
end Absolute;
--------------
-- Relative --
--------------
overriding procedure Relative (Self : in out Task_Cursor; Step : Integer) is
begin
Self.Position := Self.Position + Step;
end Relative;
----------
-- Find --
----------
procedure Find (Self : Abstract_Cursor_Access; Value : String) is
TC : Task_Cursor_Access;
C : String_Indexes.Cursor;
Not_Indexed : constant String := "Cursor is not indexed.";
begin
if Self.all not in Task_Cursor'Class then
raise Constraint_Error with Not_Indexed;
end if;
TC := Task_Cursor_Access (Self);
if TC.Data.Get.Index.Length = 0
and then TC.Data.Get.Table.Length > 0
then
raise Constraint_Error with Not_Indexed;
end if;
C := TC.Data.Get.Index.Find (To_XString (Value));
if String_Indexes.Has_Element (C) then
TC.Position := String_Indexes.Element (C);
else
TC.Position := TC.Data.Get.Table.Last_Index + 1;
end if;
end Find;
-----------
-- Value --
-----------
overriding function Value
(Self : Task_Cursor; Field : Field_Index) return String is
begin
return To_String (Self.Data.Get.Table (Self.Position) (Field).Element);
end Value;
-------------------
-- XString_Value --
-------------------
overriding function XString_Value
(Self : Task_Cursor; Field : Field_Index) return XString is
begin
return Self.Data.Get.Table (Self.Position) (Field).Element;
end XString_Value;
------------------------
-- Task_Safe_Instance --
------------------------
function Task_Safe_Instance
(Source : Abstract_Cursor_Access;
Index_By : Field_Index'Base := No_Field_Index)
return Abstract_Cursor_Access
is
Src : DBMS_Forward_Cursor'Class renames
DBMS_Forward_Cursor'Class (Source.all);
Result : Task_Cursor_Access;
Row : Record_Type (Field_Index'First
.. Src.Field_Count + Field_Index'First - 1);
procedure Append_Index_Element (Row : Positive);
--------------------------
-- Append_Index_Element --
--------------------------
procedure Append_Index_Element (Row : Positive) is
CS : String_Indexes.Cursor;
OK : Boolean;
Ref : constant Data_Set_Pointers.Reference_Type := Result.Data.Get;
begin
Ref.Index.Insert (Ref.Table (Row)(Index_By).Element, Row, CS, OK);
if not OK then
Trace
(Me_Error,
"Field " & Src.Field_Name (Index_By) & " value "
& To_String (Ref.Table (Row)(Index_By).Element)
& " is not unique. Not all records indexed.");
-- We could support a few records response on one Find call
-- over the Next after Find.
end if;
end Append_Index_Element;
begin
if Source.all in Task_Cursor'Class then
Result := Task_Cursor_Access (Source);
if Index_By >= Field_Index'First
and then Result.Data.Get.Index.Is_Empty
then
for J in 1 .. Result.Data.Get.Table.Last_Index loop
Append_Index_Element (J);
end loop;
end if;
return Source;
end if;
Result := new Task_Cursor;
Result.Data.Set (Data_Set'
(Error => To_XString (Src.Error_Msg),
Status => To_XString (Src.Status),
Success => Src.Is_Success,
TID => Current_Task,
Table => <>,
Index => <>));
for J in Row'Range loop
Row (J).Element := To_XString (Src.Field_Name (J));
end loop;
Result.Data.Get.Table.Append (Row);
while Src.Has_Row loop
for J in Row'Range loop
if Src.Is_Null (J) then
Row (J) := (Null_XString, True);
else
Row (J).Element := To_XString (Src.Value (J));
Row (J).Empty := False;
end if;
end loop;
Result.Data.Get.Table.Append (Row);
if Index_By >= Field_Index'First then
Append_Index_Element (Result.Data.Get.Table.Last_Index);
end if;
Src.Next;
end loop;
if Src in DBMS_Direct_Cursor'Class then
First (DBMS_Direct_Cursor'Class (Src));
end if;
return Abstract_Cursor_Access (Result);
end Task_Safe_Instance;
function Task_Safe_Instance
(Source : Forward_Cursor'Class;
Index_By : Field_Index'Base := No_Field_Index) return Direct_Cursor
is
Target : constant Abstract_Cursor_Access :=
Task_Safe_Instance (Source.Res, Index_By);
begin
if Target = Source.Res then
return Direct_Cursor (Source);
end if;
return (Ada.Finalization.Controlled with Res => Target);
end Task_Safe_Instance;
---------------------
-- Task_Safe_Clone --
---------------------
function Task_Safe_Clone (Source : Direct_Cursor) return Direct_Cursor is
TC : constant Task_Cursor_Access := Task_Cursor_Access (Source.Res);
begin
if TC.Data.Get.TID = Current_Task then
-- Do not need to create a copy for the task where the cursor was
-- created. But we need to reset the position because it was changed
-- by the previous user.
TC.First;
return Source;
end if;
return (Ada.Finalization.Controlled with
Res => new Task_Cursor'
(Abstract_DBMS_Forward_Cursor with
Position => 1, Data => TC.Data));
end Task_Safe_Clone;
end GNATCOLL.SQL.Exec.Tasking;