1------------------------------------------------------------------------------
2--                                                                          --
3--                          APQ DATABASE BINDINGS                           --
4--                                                                          --
5--                            A P Q - POSTGRESQL  			    --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--         Copyright (C) 2002-2007, Warren W. Gay VE3WWG                    --
10--         Copyright (C) 2007-2011, KOW Framework Project                   --
11--                                                                          --
12--                                                                          --
13-- APQ is free software;  you can  redistribute it  and/or modify it under  --
14-- terms of the  GNU General Public License as published  by the Free Soft- --
15-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
16-- sion.  APQ is distributed in the hope that it will be useful, but WITH-  --
17-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
18-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
19-- for  more details.  You should have  received  a copy of the GNU General --
20-- Public License  distributed with APQ;  see file COPYING.  If not, write  --
21-- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
22-- MA 02111-1307, USA.                                                      --
23--                                                                          --
24-- As a special exception,  if other files  instantiate  generics from this --
25-- unit, or you link  this unit with other files  to produce an executable, --
26-- this  unit  does not  by itself cause  the resulting  executable  to  be --
27-- covered  by the  GNU  General  Public  License.  This exception does not --
28-- however invalidate  any other reasons why  the executable file  might be --
29-- covered by the  GNU Public License.                                      --
30------------------------------------------------------------------------------
31
32with Ada.Exceptions;
33with Ada.Calendar;
34with Ada.Unchecked_Deallocation;
35with Ada.Unchecked_Conversion;
36with Ada.Characters.Latin_1;
37with Ada.Characters.Handling;
38with Ada.Strings.Fixed;
39with ada.strings.maps;
40with Ada.IO_Exceptions;
41with System;
42with System.Address_To_Access_Conversions;
43with Interfaces.C.Strings;
44with GNAT.OS_Lib;
45
46use Interfaces.C;
47use Ada.Exceptions;
48
49package body APQ.PostgreSQL.Client is
50
51	Seek_Set : constant Interfaces.C.int := 0;
52	Seek_Cur : constant Interfaces.C.int := 1;
53	Seek_End : constant Interfaces.C.int := 2;
54	No_Date : Ada.Calendar.Time;
55
56	type PQ_Status_Type is (
57		Connection_OK,
58		Connection_Bad,
59		Connection_Started,		-- Waiting for connection to be made.
60		Connection_Made,		-- Connection OK; waiting to send.
61		Connection_Awaiting_Response,	-- Waiting for a response
62		Connection_Auth_OK,		-- Received authentication
63                         Connection_Setenv,		-- Negotiating environment.
64                         Connection_ssl_startup,
65                         Connection_needed
66	);
67
68	for PQ_Status_Type use (
69		0,	-- CONNECTION_OK
70		1,	-- CONNECTION_BAD
71		2,	-- CONNECTION_STARTED
72		3,	-- CONNECTION_MADE
73		4,	-- CONNECTION_AWAITING_RESPONSE
74		5,	-- CONNECTION_AUTH_OK
75                6,	-- CONNECTION_SETENV
76                7,        -- Connection_ssl_startup
77                8         -- Connection_needed
78
79                        );
80   pragma convention(C,PQ_Status_Type);
81
82
83	------------------------------
84	-- DATABASE CONNECTION :
85	------------------------------
86
87
88	function Engine_Of(C : Connection_Type) return Database_Type is
89	begin
90		return Engine_PostgreSQL;
91	end Engine_Of;
92
93
94
95	function New_Query(C : Connection_Type) return Root_Query_Type'Class is
96		Q : Query_Type;
97	begin
98		return Q;
99	end New_Query;
100
101
102
103	procedure Notify_on_Standard_Error(C : in out Connection_Type; Message : String) is
104		use Ada.Text_IO;
105	begin
106		Put(Standard_Error,"*** NOTICE : ");
107		Put_Line(Standard_Error,Message);
108	end Notify_on_Standard_Error;
109
110
111
112	procedure Set_Instance(C : in out Connection_Type; Instance : String) is
113	begin
114		Raise_Exception(Not_Supported'Identity,
115			"PG01: PostgreSQL has no Instance ID. (Set_Instance)");
116	end Set_Instance;
117
118
119
120	function Host_Name(C : Connection_Type) return String is
121	begin
122		if not Is_Connected(C) then
123			return Host_Name(Root_Connection_Type(C));
124		else
125			declare
126				use Interfaces.C.Strings;
127				function PQhost(PGconn : PG_Conn) return chars_ptr;
128				pragma Import(C,PQhost,"PQhost");
129
130				The_Host : chars_ptr := PQhost(C.Connection);
131			begin
132				if The_Host = Null_Ptr then
133					return "localhost";
134				end if;
135				return Value_Of(The_Host);
136			end;
137		end if;
138	end Host_Name;
139
140
141
142	function Port(C : Connection_Type) return Integer is
143	begin
144		if not Is_Connected(C) then
145			return Port(Root_Connection_Type(C));
146		else
147			declare
148				use Interfaces.C.Strings;
149				function PQport(PGconn : PG_Conn) return chars_ptr;
150				pragma Import(C,PQport,"PQport");
151
152				The_Port : String := Value_Of(PQport(C.Connection));
153			begin
154				return Integer'Value(The_Port);
155			exception
156				when others =>
157					Raise_Exception(Invalid_Format'Identity,
158						"PG02: Invalid port number or is a UNIX socket reference (Port).");
159			end;
160		end if;
161
162		return 0;
163	end Port;
164
165
166
167	function Port(C : Connection_Type) return String is
168	begin
169		if not Is_Connected(C) then
170			return Port(Root_Connection_Type(C));
171		else
172			declare
173				use Interfaces.C.Strings;
174				function PQport(PGconn : PG_Conn) return chars_ptr;
175				pragma Import(C,PQport,"PQport");
176			begin
177				return Value_Of(PQport(C.Connection));
178			end;
179		end if;
180
181	end Port;
182
183
184
185	function DB_Name(C : Connection_Type) return String is
186	begin
187		if not Is_Connected(C) then
188			return To_Case(DB_Name(Root_Connection_Type(C)),C.SQL_Case);
189		else
190			declare
191				use Interfaces.C.Strings;
192				function PQdb(PGconn : PG_Conn) return chars_ptr;
193				pragma Import(C,PQdb,"PQdb");
194			begin
195				return Value_Of(PQdb(C.Connection));
196			end;
197		end if;
198
199	end DB_Name;
200
201
202
203	function User(C : Connection_Type) return String is
204	begin
205		if not Is_Connected(C) then
206			return User(Root_Connection_Type(C));
207		else
208			declare
209				use Interfaces.C.Strings;
210				function PQuser(PGconn : PG_Conn) return chars_ptr;
211				pragma Import(C,PQuser,"PQuser");
212			begin
213				return Value_Of(PQuser(C.Connection));
214			end;
215		end if;
216	end User;
217
218
219
220	function Password(C : Connection_Type) return String is
221	begin
222		if not Is_Connected(C) then
223			return Password(Root_Connection_Type(C));
224		else
225			declare
226				use Interfaces.C.Strings;
227				function PQpass(PGconn : PG_Conn) return chars_ptr;
228				pragma Import(C,PQpass,"PQpass");
229			begin
230				return Value_Of(PQpass(C.Connection));
231			end;
232		end if;
233	end Password;
234
235
236
237   procedure Set_DB_Name(C : in out Connection_Type; DB_Name : String) is
238
239      procedure Use_Database(C : in out Connection_Type; DB_Name : String) is
240         Q : Query_Type;
241      begin
242         begin
243            Prepare(Q,To_Case("USE " & DB_Name,C.SQL_Case));
244            Execute(Q,C);
245         exception
246            when SQL_Error =>
247               Raise_Exception(APQ.Use_Error'Identity,
248                               "PG03: Unable to select database " & DB_Name & ". (Use_Database)");
249         end;
250      end Use_Database;
251
252   begin
253      if not Is_Connected(C) then
254         -- Modify context to connect to this database when we connect
255         Set_DB_Name(Root_Connection_Type(C),DB_Name);
256      else
257         -- Use this database now
258         Use_Database(C,DB_Name);
259         -- Update context info if no exception thrown above
260         Set_DB_Name(Root_Connection_Type(C),DB_Name);
261      end if;
262
263      C.keyname_val_cache_uptodate := false;
264
265   end Set_DB_Name;
266
267
268
269   procedure Set_Options(C : in out Connection_Type; Options : String) is
270   begin
271      Replace_String(C.Options,Set_Options.Options);
272      C.keyname_val_cache_uptodate := false;
273   end Set_Options;
274
275
276
277   function Options(C : Connection_Type) return String is
278   begin
279      if not Is_Connected(C) then
280         if C.Options /= null then
281            return C.Options.all;
282         end if;
283      else
284         declare
285            use Interfaces.C.Strings;
286            function PQoptions(PGconn : PG_Conn) return chars_ptr;
287            pragma Import(C,PQoptions,"PQoptions");
288         begin
289            return Value_Of(PQoptions(C.Connection));
290         end;
291      end if;
292      return "";
293   end Options;
294
295
296
297	procedure Set_Notify_Proc(C : in out Connection_Type; Notify_Proc : Notify_Proc_Type) is
298	begin
299		C.Notify_Proc := Set_Notify_Proc.Notify_Proc;
300	end Set_Notify_Proc;
301
302
303
304	function Notify_Proc(C : Connection_Type) return Notify_Proc_Type is
305	begin
306		return C.Notify_Proc;
307	end Notify_Proc;
308
309
310	--------------------------------------------------
311	-- Connection_Notify is called by notices.c as
312	-- a callback from the libpq interface.
313	--------------------------------------------------
314--  	procedure Connection_Notify(C_Addr : System.Address; Msg_Ptr : Interfaces.C.Strings.chars_ptr);
315--  	pragma Export(C,Connection_Notify,"Connection_Notify");
316
317
318	procedure Connection_Notify(C_Addr : System.Address; Msg_Ptr : Interfaces.C.Strings.chars_ptr) is
319		use Interfaces.C.Strings;
320		package Addr is new System.Address_To_Access_Conversions(Connection_Type);
321
322		function Strip_Prefix(S : String) return String is
323			use Ada.Strings.Fixed, Ada.Strings;
324		begin
325			if S(S'First..S'First+6) = "NOTICE:" then
326				return Trim(S(S'First+7..S'Last),Left);
327			end if;
328			return S;
329		end Strip_Prefix;
330
331		Abrt_Notice :	constant String := "current transaction is aborted, queries ignored until end of transaction block";
332		Conn :		Addr.Object_Pointer := Addr.To_Pointer(C_Addr);
333		Msg :		String := Strip_Prefix(Strip_NL(To_Ada_String(Msg_Ptr)));
334	begin
335		if Conn.Notice /= null then
336			Free(Conn.Notice);		-- Free last notice
337		end if;
338		-- Store new notice
339		Conn.Notice := new String(1..Msg'Length);
340		Conn.Notice.all := Msg;
341
342		if Conn.Notice.all = Abrt_Notice then
343			Conn.Abort_State := True;
344		end if;
345
346		if Conn.Notify_Proc /= Null then
347			Conn.Notify_Proc(Conn.all,Conn.Notice.all);
348		end if;
349
350	end Connection_Notify;
351
352
353
354	function PQ_Status(C : Connection_Type) return PQ_Status_Type is
355		function PQstatus(C : PG_Conn) return PQ_Status_Type;
356		pragma Import(C,PQstatus,"PQstatus");
357	begin
358		if C.Connection = Null_Connection then
359			return Connection_Bad;
360		else
361			return PQstatus(C.Connection);
362		end if;
363	end PQ_Status;
364
365	procedure Disconnect(C : in out Connection_Type) is
366		procedure Notice_Uninstall(C : PG_Conn);
367		pragma Import(C,notice_uninstall,"notice_uninstall");
368		procedure PQfinish(C : PG_Conn);
369		pragma Import(C,PQfinish,"PQfinish");
370	begin
371
372		if not Is_Connected(C) then
373			Raise_Exception(Not_Connected'Identity,
374				"PG09: Not connected. (Disconnect)");
375		end if;
376
377		Notice_Uninstall(C.Connection);		-- Disconnect callback notices
378		PQfinish(C.Connection);			-- Now release the connection
379		C.Connection  := Null_Connection;
380		C.Abort_State := False;			-- Clear abort state
381		C.Notify_Proc := null;			-- De-register the notify procedure
382
383		if C.Trace_Mode = Trace_APQ or else C.Trace_Mode = Trace_Full then
384			Ada.Text_IO.Put_Line(C.Trace_Ada,"-- DISCONNECT");
385		end if;
386
387		Reset(C);
388
389	end Disconnect;
390
391
392
393	function Is_Connected(C : Connection_Type) return Boolean is
394	begin
395		return PQ_Status(C) = Connection_OK;
396	end Is_Connected;
397
398
399
400	procedure Internal_Reset(C : in out Connection_Type; In_Finalize : Boolean := False) is
401	begin
402		Free_Ptr(C.Error_Message);
403
404		if C.Connection /= Null_Connection then
405			declare
406				Q : Query_Type;
407			begin
408				Clear_Abort_State(C);
409				if C.Rollback_Finalize or In_Abort_State(C) then
410					if C.Trace_On and then C.Trace_Filename /= null and then In_Finalize = True then
411						Ada.Text_IO.Put_Line(C.Trace_Ada,"-- ROLLBACK ON FINALIZE");
412					end if;
413					Rollback_Work(Q,C);
414				else
415					if C.Trace_On and then C.Trace_Filename /= null and then In_Finalize = True then
416						Ada.Text_IO.Put_Line(C.Trace_Ada,"-- COMMIT ON FINALIZE");
417					end if;
418					Commit_Work(Q,C);
419				end if;
420			exception
421				when others =>
422					null;		-- Ignore if the Rollback/commit fails
423			end;
424
425			Clear_Abort_State(C);
426
427			Disconnect(C);
428
429			if C.Trace_Filename /= null then
430				Close_DB_Trace(C);
431			end if;
432
433		end if;
434
435      if C.Connection = Null_Connection then
436         Free_Ptr(C.Host_Name);
437         Free_Ptr(C.Host_Address);
438         Free_Ptr(C.DB_Name);
439         Free_Ptr(C.User_Name);
440         Free_Ptr(C.User_Password);
441         Free_Ptr(C.Options);
442         Free_Ptr(C.Error_Message);
443         Free_Ptr(C.Notice);
444         --
445         clear_all_key_nameval(c);
446
447      end if;
448   end Internal_Reset;
449
450
451
452	procedure Reset(C : in out Connection_Type) is
453	begin
454		Internal_Reset(C,In_Finalize => False);
455	end Reset;
456
457
458
459	function Error_Message(C : Connection_Type) return String is
460		function PQerrorMessage(C : PG_Conn) return Interfaces.C.Strings.chars_ptr;
461		pragma Import(C,PQerrorMessage,"PQerrorMessage");
462	begin
463		if C.Connection = Null_Connection then
464			if C.Error_Message /= null then
465				return C.Error_Message.all;
466			else
467				return "";
468			end if;
469		else
470			return To_Ada_String(PQerrorMessage(C.Connection));
471		end if;
472	end Error_Message;
473
474
475
476	function Notice_Message(C : Connection_Type) return String is
477	begin
478		if C.Notice /= null then
479			return C.Notice.all;
480		end if;
481		return "";
482   end Notice_Message;
483   --
484   --
485   function "="( Left :root_option_record2; right : root_option_record2) return boolean
486   is
487      pragma Optimize(time);
488
489      lkey_s : string :=
490	ada.Strings.fixed.Trim( ada.Characters.Handling.To_Lower(
491	  ada.Strings.Unbounded.To_String( left.key_u)) ,
492	  ada.Strings.Both );
493      rkey_s : string :=
494	ada.Strings.fixed.Trim( ada.Characters.Handling.To_Lower(
495	  ada.Strings.Unbounded.To_String( right.key_u)) ,
496	  ada.Strings.Both );
497   begin
498      if lkey_s = rkey_s then
499	 return true;
500      end if;
501      return false;
502   end "=";
503
504   function quote_string( qkv : string ) return String
505   is
506      use ada.Strings;
507      use ada.Strings.Fixed;
508
509      function PQescapeString(to, from : System.Address; length : size_t) return size_t;
510      pragma Import(C,PQescapeString,"PQescapeString");
511      src : string := trim ( qkv , both );
512      C_Length : size_t := src'Length * 2 + 1;
513      C_From   : char_array := To_C(src);
514      C_To     : char_array(0..C_Length-1);
515      R_Length : size_t := PQescapeString(C_To'Address,C_From'Address,C_Length);
516      -- viva!!! :-)
517   begin
518      return To_Ada(C_To);
519   end quote_string;
520   ----
521
522   function quote_string( qkv : string ) return ada.Strings.Unbounded.Unbounded_String
523   is
524   begin
525      return ada.Strings.Unbounded.To_Unbounded_String(String'(quote_string(qkv)));
526   end quote_string;
527   --
528   function cache_key_nameval_uptodate( C : Connection_Type) --
529                                       return boolean
530   is
531   begin
532      return c.keyname_val_cache_uptodate;
533   end cache_key_nameval_uptodate;
534
535   --
536   procedure cache_key_nameval_create( C : in out Connection_Type; force : boolean := false)--
537   is
538      pragma optimize(time);
539      use ada.strings.Unbounded;
540      use ada.strings.Fixed;
541      use ada.Strings;
542      use Ada.Characters.Handling;
543
544      use apq.postgresql.client.options_list2;
545      --
546      tmp_ub_cache : Unbounded_String := To_Unbounded_String(160); -- pre-allocate :-)
547      tmp_eq : Unbounded_String := to_Unbounded_String(" = '");
548      tmp_ap : Unbounded_String := to_Unbounded_String("' ");
549      --
550      procedure process(position : cursor) is
551	 val_tmp : root_option_record2 := element(position);
552      begin
553	 if val_tmp.is_valid = false then return; end if; --bahiii! :-)
554
555	 tmp_ub_cache := tmp_ub_cache & val_tmp.key_u & tmp_eq &
556	   trim(Unbounded_String'(quote_string(string'(To_String(val_tmp.value_u)))),ada.Strings.Both)
557	   & tmp_ap ;
558
559      end process;
560
561   begin
562      if cache_key_nameval_uptodate( C ) and force = false then return; end if; -- bahiii :-)
563      c.keyname_val_cache := To_Unbounded_String("");
564
565      if c.Port_Format = UNIX_Port then
566         tmp_ub_cache := to_Unbounded_String("host")
567           & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.Host_Name)))),ada.Strings.both) & tmp_ap
568           & to_Unbounded_String("port")
569           & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.Port_Name)))),ada.Strings.both) & tmp_ap ;
570        elsif c.Port_Format = IP_Port then
571         tmp_ub_cache := to_Unbounded_String("hostaddr")
572           & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.Host_Address)))),ada.Strings.both) & tmp_ap
573           & to_Unbounded_String("port")
574           & tmp_eq & trim(to_Unbounded_String(string'(Port_Integer'image(c.Port_Number))),ada.Strings.both) & tmp_ap;
575      else
576         raise program_error;
577      end if;
578
579      tmp_ub_cache := tmp_ub_cache
580        & to_Unbounded_String("dbname") & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.DB_Name)))),ada.Strings.both) & tmp_ap
581        & to_Unbounded_String("user") & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.User_Name)))),ada.Strings.both) & tmp_ap
582        & to_Unbounded_String("password") & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.User_Password)))),ada.Strings.both) & tmp_ap;
583      if trim(string'(To_String(C.Options)), ada.Strings.Both) /= "" then
584         tmp_ub_cache := tmp_ub_cache
585         & to_Unbounded_String("options") & tmp_eq & trim(Unbounded_String'(quote_string(string'(To_String(C.Options)))), both) & tmp_ap ;
586      end if;
587
588      if not (c.key_name_list.Is_Empty ) then
589	 c.key_name_list.Iterate(process'Access);
590      end if;
591
592      c.keyname_val_cache := tmp_ub_cache;
593
594      tmp_ub_cache := To_Unbounded_String("");
595
596   end cache_key_nameval_create;--
597   --
598   procedure clear_all_key_nameval(C : in out Connection_Type )
599   is
600      pragma optimize(time);
601   begin
602      if not ( c.key_name_list.is_empty ) then
603	    c.key_name_list.clear;
604      end if;
605      c.keyname_val_cache := ada.Strings.Unbounded.To_Unbounded_String("");
606      c.keyname_val_cache_uptodate := false;
607
608   end clear_all_key_nameval;
609
610   procedure key_nameval( L : in out options_list2.list ;
611			 val : root_option_record2;
612			 clear : boolean := false
613			)
614   is
615      use options_list2;
616      mi_cursor : options_list2.cursor := no_element;
617   begin
618      if clear then
619	 if not ( L.is_empty ) then
620	    L.clear;
621	 end if;
622      end if;
623      if L.is_empty then
624	 L.append(val);
625	 return;
626      end if;
627      mi_cursor := L.find(val);
628      if mi_cursor = No_Element then
629	 L.append(val);
630	 return;
631      end if;
632      L.replace_element(mi_cursor, val);
633
634   end key_nameval;
635
636
637   procedure add_key_nameval( C : in out Connection_Type;
638                             kname, kval : string := "";
639                             clear : boolean := false )
640   is
641      pragma optimize(time);
642      use ada.strings;
643      use ada.Strings.Fixed;
644
645      tmp_kname : string  := string'(trim(kname,both));
646      tmp_kval  : string  := string'(trim(kval,both));
647
648   begin
649      if tmp_kname = "" then return; end if; -- bahiii :-)
650      declare
651	 val_tmp : root_option_record2 :=
652	   root_option_record2'(is_valid => true,
653			 key_u    => ada.Strings.Unbounded.To_Unbounded_String(tmp_kname),
654			 value_u  => ada.Strings.Unbounded.To_Unbounded_String(tmp_kval)
655			);
656      begin
657	 key_nameval(L     => c.key_name_list,
658	      val   => val_tmp ,
659	      clear => clear);
660      end;
661      C.keyname_val_cache_uptodate := false;
662
663   end add_key_nameval;
664
665 --
666   procedure clone_clone_pg(To : in out Connection_Type; From : Connection_Type )
667   is
668      pragma optimize(time);
669      use apq.postgresql.client.options_list2;
670      --
671      procedure add(position : cursor) is
672      begin
673	 to.key_name_list.append(element(position));
674      end add;
675
676   begin
677      clear_all_key_nameval(to);
678
679      if not ( from.key_name_list.is_empty ) then
680	    from.key_name_list.iterate(add'Access);
681      end if;
682
683      to.keyname_val_cache_uptodate := false;
684
685   end clone_clone_pg;
686
687   --
688   procedure connect(C : in out Connection_Type; Check_Connection : Boolean := True)
689   is
690      pragma optimize(time);
691
692      use Interfaces.C.Strings;
693
694   begin
695      if Check_Connection and then Is_Connected(C) then
696         Raise_Exception(Already_Connected'Identity,
697                         "PG07: Already connected (Connect).");
698      end if;
699
700      cache_key_nameval_create(C); -- don't worry :-) "re-create" accours only if not uptodate :-)
701                                   -- This procedure can be executed manually if you desire :-)
702                                   -- "for example": the "Connection_type" var was created  and configured
703                                   -- much before the  connection with the DataBase server :-) take place
704                                   -- then the "Connection_type" already uptodate
705                                   -- ( well... uptodate if really uptodate ;-)
706                                   -- this will speedy up the things a little :-)
707      declare
708         procedure Notice_Install(Conn : PG_Conn; ada_obj_ptr : System.Address);
709         pragma import(C,Notice_Install,"notice_install");
710
711         function PQconnectdb(coni : chars_ptr ) return PG_Conn;
712         pragma import(C,PQconnectdb,"PQconnectdb");
713         coni_str : string := ada.Strings.Unbounded.To_String(C.keyname_val_cache);
714         C_conni : chars_ptr := New_String(Str => coni_str );
715      begin
716         C.Connection := PQconnectdb( C_conni); -- blocking call :-)
717         Free_Ptr(C.Error_Message);
718
719         if PQ_Status(C) /= Connection_OK then  -- if the connecting in a non-blocking fashion,
720            -- there are more option of status needing verification :-)
721            -- it Don't the case here
722            declare
723               procedure PQfinish(C : PG_Conn);
724               pragma Import(C,PQfinish,"PQfinish");
725               Msg : String := Strip_NL(Error_Message(C));
726            begin
727               PQfinish(C.Connection);
728               C.Connection := Null_Connection;
729               C.Error_Message := new String(1..Msg'Length);
730               C.Error_Message.all := Msg;
731               Raise_Exception(Not_Connected'Identity,
732                               "PG08: Failed to connect to database server (Connect). error was: " &
733                               msg ); -- more descriptive about 'what failed' :-)
734            end;
735         end if;
736
737         Notice_Install(C.Connection,C'Address);	-- Install Connection_Notify handler
738
739         ------------------------------
740         -- SET PGDATESTYLE TO ISO;
741         --
742         -- This is necessary for all of the
743         -- APQ date handling routines to
744         -- function correctly. This implies
745         -- that all APQ applications programs
746         -- should use the ISO date format.
747         ------------------------------
748         declare
749            SQL : Query_Type;
750         begin
751            Prepare(SQL,"SET DATESTYLE TO ISO");
752            Execute(SQL,C);
753         exception
754            when Ex : others =>
755               Disconnect(C);
756               Reraise_Occurrence(Ex);
757         end;
758      end;
759
760   end connect;
761
762   procedure connect(C : in out Connection_Type; Same_As : Root_Connection_Type'Class)
763   is
764      pragma optimize(time);
765
766      type Info_Func is access function(C : Connection_Type) return String;
767
768      procedure Clone(S : in out String_Ptr; Get_Info : Info_Func) is
769         Info : String := Get_Info(Connection_Type(Same_As));
770      begin
771         if Info'Length > 0 then
772            S	:= new String(1..Info'Length);
773            S.all	:= Info;
774         else
775            null;
776            pragma assert(S = null);
777         end if;
778      end Clone;
779      blo : boolean := true;
780      tmpex : natural := 2;
781   begin
782      Reset(C);
783
784      Clone(C.Host_Name,Host_Name'Access);
785
786      C.Port_Format := Same_As.Port_Format;
787      if C.Port_Format = IP_Port then
788         C.Port_Number := Port(Same_As);	  -- IP_Port
789      else
790         Clone(C.Port_Name,Port'Access);	  -- UNIX_Port
791      end if;
792
793      Clone(C.DB_Name,DB_Name'Access);
794      Clone(C.User_Name,User'Access);
795      Clone(C.User_Password,Password'Access);
796      Clone(C.Options,Options'Access);
797
798      C.Rollback_Finalize	:= Same_As.Rollback_Finalize;
799      C.Notify_Proc		:= Connection_Type(Same_As).Notify_Proc;
800      -- I believe if "Same_As" var is defacto a "Connection_Type" as "C" var,
801      -- there are need for copy  key's name and val from "Same_As" ,
802      -- because in this keys and vals
803      -- maybe are key's how sslmode , gsspi etc, that are defacto needs for connecting "C"
804
805      if Same_As.Engine_Of = Engine_PostgreSQL then
806         clone_clone_pg(C , Connection_Type(Same_as));
807      end if;
808
809     connect(C);	-- Connect to database before worrying about trace facilities
810
811      -- TRACE FILE & TRACE SETTINGS ARE NOT CLONED
812
813   end connect;
814
815   function verifica_conninfo_cache( C : Connection_Type) return string -- for debug purpose :-P
816                                                                        -- in the spirit there are an get_password(c) yet...
817
818   is
819   begin
820      return ada.Strings.Unbounded.To_String(c.keyname_val_cache);
821   end verifica_conninfo_cache;
822
823
824
825
826	procedure Open_DB_Trace(C : in out Connection_Type; Filename : String; Mode : Trace_Mode_Type := Trace_APQ) is
827	begin
828		if C.Trace_Filename /= null then
829			Raise_Exception(Tracing_State'Identity,
830				"PG04: Already in a tracing state (Open_DB_Trace).");
831		end if;
832
833		if not Is_Connected(C) then
834			Raise_Exception(Not_Connected'Identity,
835				"PG05: Not connected (Open_DB_Trace).");
836		end if;
837
838		if Mode = Trace_None then
839			pragma assert(C.Trace_Mode = Trace_None);
840			return;	  -- No trace required
841		end if;
842
843		declare
844			use CStr, System, Ada.Text_IO, Ada.Text_IO.C_Streams;
845			procedure PQtrace(PGconn : PG_Conn; debug_port : CStr.FILEs);
846			pragma Import(C,PQtrace,"PQtrace");
847
848			C_Filename :	char_array := To_C(Filename);
849			File_Mode :	char_array := To_C("a");
850		begin
851			C.Trace_File := fopen(C_Filename'Address,File_Mode'Address);
852			if C.Trace_File = Null_Stream then
853				Raise_Exception(Ada.IO_Exceptions.Name_Error'Identity,
854					"PG06: Unable to open trace file " & Filename & " (Open_DB_Trace).");
855			end if;
856
857			Open(C.Trace_Ada,Append_File,C.Trace_File,Form => "shared=yes");
858			Ada.Text_IO.Put_Line(C.Trace_Ada,"-- Start of Trace, Mode = " & Trace_Mode_Type'Image(Mode));
859
860			if Mode = Trace_DB or Mode = Trace_Full then
861				PQtrace(C.Connection,C.Trace_File);
862			end if;
863
864		end;
865
866		C.Trace_Filename	:= new String(1..Filename'Length);
867		C.Trace_Filename.all	:= Filename;
868		C.Trace_Mode		:= Mode;
869		C.Trace_On		:= True;		-- Enabled by default until Set_Trace disables this
870
871	end Open_DB_Trace;
872
873
874
875	procedure Close_DB_Trace(C : in out Connection_Type) is
876	begin
877
878		if C.Trace_Mode = Trace_None then
879			return;		-- No tracing in progress
880		end if;
881
882		pragma assert(C.Trace_Filename /= null);
883
884		declare
885			use CStr;
886			procedure PQuntrace(PGconn : PG_Conn);
887			pragma Import(C,PQuntrace,"PQuntrace");
888		begin
889			if C.Trace_Mode = Trace_DB or C.Trace_Mode = Trace_Full then
890				PQuntrace(C.Connection);
891			end if;
892
893			Free(C.Trace_Filename);
894
895			Ada.Text_IO.Put_Line(C.Trace_Ada,"-- End of Trace.");
896			Ada.Text_IO.Close(C.Trace_Ada);	-- This closes C.Trace_File too
897
898			C.Trace_Mode	:= Trace_None;
899			C.Trace_On	:= True;		-- Restore default
900		end;
901
902	end Close_DB_Trace;
903
904
905
906	procedure Set_Trace(C : in out Connection_Type; Trace_On : Boolean := True) is
907		procedure PQtrace(PGconn : PG_Conn; debug_port : CStr.FILEs);
908		procedure PQuntrace(PGconn : PG_Conn);
909		pragma Import(C,PQtrace,"PQtrace");
910		pragma Import(C,PQuntrace,"PQuntrace");
911
912		Orig_Trace : Boolean := C.Trace_On;
913	begin
914		C.Trace_On := Set_Trace.Trace_On;
915
916		if Orig_Trace = C.Trace_On then
917			return;		-- No change
918		end if;
919
920		if C.Trace_On then
921			if C.Trace_Mode = Trace_DB or C.Trace_Mode = Trace_Full then
922				PQtrace(C.Connection,C.Trace_File);		-- Enable libpq tracing
923			end if;
924		else
925			if C.Trace_Mode = Trace_DB or C.Trace_Mode = Trace_Full then
926				PQuntrace(C.Connection);			-- Disable libpq tracing
927			end if;
928		end if;
929	end Set_Trace;
930
931
932
933	function Is_Trace(C : Connection_Type) return Boolean is
934	begin
935		return C.Trace_On;
936	end Is_Trace;
937
938
939
940	function In_Abort_State(C : Connection_Type) return Boolean is
941	begin
942		if C.Connection = Null_Connection then
943			return False;
944		end if;
945		return C.Abort_State;
946	end In_Abort_State;
947
948
949
950	------------------------------
951	-- SQL QUERY API :
952	------------------------------
953
954
955	procedure Free(R : in out PQ_Result) is
956		procedure PQclear(R : PQ_Result);
957		pragma Import(C,PQclear,"PQclear");
958	begin
959		if R /= Null_Result then
960			PQclear(R);
961			R := Null_Result;
962		end if;
963	end Free;
964
965
966
967	procedure Clear(Q : in out Query_Type) is
968	begin
969		Free(Q.Result);
970		Clear(Root_Query_Type(Q));
971	end Clear;
972
973
974
975	procedure Append_Quoted(Q : in out Query_Type; Connection : Root_Connection_Type'Class; SQL : String; After : String := "") is
976		function PQescapeString(to, from : System.Address; length : size_t) return size_t;
977		pragma Import(C,PQescapeString,"PQescapeString");
978		C_Length :	size_t := SQL'Length * 2 + 1;
979		C_From :	char_array := To_C(SQL);
980		C_To :		char_array(0..C_Length-1);
981		R_Length :	size_t := PQescapeString(C_To'Address,C_From'Address,C_Length);
982	begin
983		Append(Q,"'" & To_Ada(C_To) & "'",After);
984		Q.Caseless(Q.Count) := False; -- Preserve case for this one
985	end Append_Quoted;
986
987
988
989	procedure Execute(Query : in out Query_Type; Connection : in out Root_Connection_Type'Class) is
990		function PQexec(C : PG_Conn; Q : System.Address) return PQ_Result;
991		pragma Import(C,PQexec,"PQexec");
992		R : Result_Type;
993	begin
994
995		Query.SQL_Case := Connection.SQL_Case;
996
997		if not Is_Connected(Connection) then
998			Raise_Exception(Not_Connected'Identity,
999				"PG14: The Connection_Type object supplied is not connected (Execute).");
1000		end if;
1001
1002		if In_Abort_State(Connection) then
1003			Raise_Exception(Abort_State'Identity,
1004				"PG15: The PostgreSQL connection is in the Abort state (Execute).");
1005		end if;
1006
1007		if Query.Result /= Null_Result then
1008			Free(Query.Result);
1009		end if;
1010
1011		declare
1012			A_Query :	String := To_String(Query);
1013			C_Query :	char_array := To_C(A_Query);
1014		begin
1015			if Connection.Trace_On then
1016				if Connection.Trace_Mode = Trace_APQ or Connection.Trace_Mode = Trace_Full then
1017					Ada.Text_IO.Put_Line(Connection.Trace_Ada,"-- SQL QUERY:");
1018					Ada.Text_IO.Put_Line(Connection.Trace_Ada,A_Query);
1019					Ada.Text_IO.Put_Line(Connection.Trace_Ada,";");
1020				end if;
1021			end if;
1022
1023			Query.Result := PQexec(Internal_Connection(Connection_Type(Connection)),C_Query'Address);
1024
1025			if Connection.Trace_On then
1026				if Connection.Trace_Mode = Trace_APQ or Connection.Trace_Mode = Trace_Full then
1027					Ada.Text_IO.Put_Line(Connection.Trace_Ada,"-- Result: '" & Command_Status(Query) & "'");
1028					Ada.Text_IO.New_Line(Connection.Trace_Ada);
1029				end if;
1030			end if;
1031		end;
1032
1033		if Query.Result /= Null_Result then
1034			Query.Tuple_Index := First_Tuple_Index;
1035			R := Result(Query);
1036			if R /= Command_OK and R /= Tuples_OK then
1037--				if Connection.Trace_On then
1038--					Ada.Text_IO.Put_Line(Connection.Trace_Ada,"-- Error " &
1039--						Result_Type'Image(Query.Error_Code) & " : " & Error_Message(Query));
1040--				end if;
1041				Raise_Exception(SQL_Error'Identity,
1042					"PG16: The query failed (Execute).");
1043			end if;
1044		else
1045--			if Connection.Trace_On then
1046--				Ada.Text_IO.Put_Line(Connection.Trace_Ada,"-- Error " &
1047--					Result_Type'Image(Query.Error_Code) & " : " & Error_Message(Query));
1048--			end if;
1049			Raise_Exception(SQL_Error'Identity,
1050				"PG17: The query failed (Execute).");
1051		end if;
1052
1053	end Execute;
1054
1055
1056
1057	procedure Execute_Checked(Query : in out Query_Type; Connection : in out Root_Connection_Type'Class; Msg : String := "") is
1058		use Ada.Text_IO;
1059	begin
1060		begin
1061			Execute(Query,Connection);
1062		exception
1063			when Ex : SQL_Error =>
1064				if Msg'Length > 0 then
1065					Put(Standard_Error,"*** SQL ERROR: ");
1066					Put_Line(Standard_Error,Msg);
1067				else
1068					Put(Standard_Error,"*** SQL ERROR IN QUERY:");
1069					New_Line(Standard_Error);
1070					Put(Standard_Error,To_String(Query));
1071					if Col(Standard_Error) > 1 then
1072						New_Line(Standard_Error);
1073					end if;
1074				end if;
1075				Put(Standard_Error,"[");
1076				Put(Standard_Error,Result_Type'Image(Result(Query)));
1077				Put(Standard_Error,": ");
1078				Put(Standard_Error,Error_Message(Query));
1079				Put_Line(Standard_Error,"]");
1080				Reraise_Occurrence(Ex);
1081			when Ex : others =>
1082				Reraise_Occurrence(Ex);
1083		end;
1084	end Execute_Checked;
1085
1086
1087
1088	procedure Begin_Work(Query : in out Query_Type; Connection : in out Root_Connection_Type'Class) is
1089	begin
1090		if In_Abort_State(Connection) then
1091			Raise_Exception(Abort_State'Identity,
1092				"PG36: PostgreSQL connection is in the abort state (Begin_Work).");
1093		end if;
1094		Clear(Query);
1095		Prepare(Query,"BEGIN WORK");
1096		Execute(Query,Connection);
1097		Clear(Query);
1098	end Begin_Work;
1099
1100
1101
1102	procedure Commit_Work(Query : in out Query_Type; Connection : in out Root_Connection_Type'Class) is
1103	begin
1104		if In_Abort_State(Connection) then
1105			Raise_Exception(Abort_State'Identity,
1106				"PG37: PostgreSQL connection is in the abort state (Commit_Work).");
1107		end if;
1108		Clear(Query);
1109		Prepare(Query,"COMMIT WORK");
1110		Execute(Query,Connection);
1111		Clear(Query);
1112	end Commit_Work;
1113
1114
1115
1116	procedure Rollback_Work(Query : in out Query_Type; Connection : in out Root_Connection_Type'Class) is
1117	begin
1118		Clear(Query);
1119		Prepare(Query,"ROLLBACK WORK");
1120		Execute(Query,Connection);
1121		Clear_Abort_State(Connection);
1122		Clear(Query);
1123	end Rollback_Work;
1124
1125
1126
1127	procedure Rewind(Q : in out Query_Type) is
1128	begin
1129		Q.Rewound := True;
1130		Q.Tuple_Index := First_Tuple_Index;
1131	end Rewind;
1132
1133
1134
1135	procedure Fetch(Q : in out Query_Type) is
1136	begin
1137		if not Q.Rewound then
1138			Q.Tuple_Index := Q.Tuple_Index + 1;
1139		else
1140			Q.Rewound := False;
1141		end if;
1142		Fetch(Q,Q.Tuple_Index);
1143	end Fetch;
1144
1145
1146
1147	procedure Fetch(Q : in out Query_Type; TX : Tuple_Index_Type) is
1148		NT : Tuple_Count_Type := Tuples(Q); -- May raise No_Result
1149	begin
1150		if NT < 1 then
1151			Raise_Exception(No_Tuple'Identity,
1152				"PG33: There is no row" & Tuple_Index_Type'Image(TX) & " (Fetch).");
1153		end if;
1154		Q.Tuple_Index := TX;
1155		Q.Rewound := False;
1156		if TX > NT then
1157			Raise_Exception(No_Tuple'Identity,
1158				"PG34: There is no row" & Tuple_Index_Type'Image(TX) & " (Fetch).");
1159		end if;
1160	end Fetch;
1161
1162
1163
1164	function End_of_Query(Q : Query_Type) return Boolean is
1165		NT : Tuple_Count_Type := Tuples(Q); -- May raise No_Result
1166	begin
1167		if NT < 1 then
1168			return True;		-- There are no tuples to return
1169		end if;
1170
1171		if Q.Rewound then
1172			return False;		-- There is at least 1 tuple to return yet
1173		end if;
1174
1175		return Tuple_Count_Type(Q.Tuple_Index) >= NT;	-- We've fetched them all
1176	end End_of_Query;
1177
1178
1179
1180	function Tuple(Q : Query_Type) return Tuple_Index_Type is
1181		NT : Tuple_Count_Type := Tuples(Q); -- May raise No_Result
1182	begin
1183		if NT < 1 or else Q.Rewound then
1184			Raise_Exception(No_Tuple'Identity,
1185				"PG35: There are no tuples to return (Tuple).");
1186		end if;
1187		return Q.Tuple_Index;
1188	end Tuple;
1189
1190
1191
1192	function Tuples(Q : Query_Type) return Tuple_Count_Type is
1193		use Interfaces.C;
1194		function PQntuples(R : PQ_Result) return int;
1195		pragma Import(C,PQntuples,"PQntuples");
1196	begin
1197		if Q.Result = Null_Result then
1198			Raise_Exception(No_Result'Identity,
1199				"PG19: There are no query results (Tuples).");
1200		end if;
1201		return Tuple_Count_Type(PQntuples(Q.Result));
1202	end Tuples;
1203
1204
1205
1206	function Columns(Q : Query_Type) return Natural is
1207		use Interfaces.C;
1208		function PQnfields(R : PQ_Result) return int;
1209		pragma Import(C,PQnfields,"PQnfields");
1210	begin
1211		if Q.Result = Null_Result then
1212			Raise_Exception(No_Result'Identity,
1213				"PG20: There are no query results (Columns).");
1214		end if;
1215		return Natural(PQnfields(Q.Result));
1216	end Columns;
1217
1218
1219
1220	function Column_Name(Q : Query_Type; CX : Column_Index_Type) return String is
1221		use Interfaces.C.Strings;
1222		function PQfname(R : PQ_Result; CBX : int) return chars_ptr;
1223		pragma Import(C,PQfname,"PQfname");
1224
1225		CBX : int := int(CX) - 1;	  -- Make zero based
1226	begin
1227		if Q.Result = Null_Result then
1228			Raise_Exception(No_Result'Identity,
1229				"PG21: There are no query results (Column_Name).");
1230		end if;
1231		declare
1232			use Interfaces.C.Strings;
1233			CP : chars_ptr := PQfname(Q.Result,CBX);
1234		begin
1235			if CP = Null_Ptr then
1236				Raise_Exception(No_Column'Identity,
1237					"PG22: There is no column CX=" & Column_Index_Type'Image(CX) & ".");
1238			end if;
1239			return To_Case(Value_Of(CP),Q.SQL_Case);
1240		end;
1241	end Column_Name;
1242
1243
1244
1245	function Column_Index(Q : Query_Type; Name : String) return Column_Index_Type is
1246		use Interfaces.C.Strings;
1247		function PQfnumber(R : PQ_Result; CBX : System.Address) return int;
1248		pragma Import(C,PQfnumber,"PQfnumber");
1249
1250		C_Name :	char_array := To_C(Name);
1251		CBX :		int := -1;
1252	begin
1253		if Q.Result = Null_Result then
1254			Raise_Exception(No_Result'Identity,
1255				"PG23: There are no query results (Column_Index).");
1256		end if;
1257		CBX := PQfnumber(Q.Result,C_Name'Address);
1258		if CBX < 0 then
1259			Raise_Exception(No_Column'Identity,
1260				"PG24: There is no column named '" & Name & " (Column_Index).");
1261		end if;
1262		return Column_Index_Type(CBX+1);
1263	end Column_Index;
1264
1265
1266
1267	function Is_Column(Q : Query_Type; CX : Column_Index_Type) return Boolean is
1268	begin
1269		if Q.Result = Null_Result then
1270			return False;
1271		end if;
1272		return Natural(CX) <= Columns(Q);
1273	end Is_Column;
1274
1275
1276
1277	function Column_Type(Q : Query_Type; CX : Column_Index_Type) return Row_ID_Type is
1278		function PQftype(R : PQ_Result; Field_Index : int) return PQOid_Type;
1279		pragma Import(C,PQftype,"PQftype");
1280		CBX : int := int(CX) - 1;
1281	begin
1282		if Q.Result = Null_Result then
1283			Raise_Exception(No_Result'Identity,
1284				"PG25: There are no query results (Column_Type).");
1285		end if;
1286		if not Is_Column(Q,CX) then
1287			Raise_Exception(No_Column'Identity,
1288				"PG26: There is no column CX=" & Column_Index_Type'Image(CX) & " (Column_Type).");
1289		end if;
1290		return Row_ID_Type(PQftype(Q.Result,CBX));
1291	end Column_Type;
1292
1293
1294
1295	function Is_Null(Q : Query_Type; CX : Column_Index_Type) return Boolean is
1296		use Interfaces.C.Strings;
1297		function PQgetisnull(R : PQ_Result; tup_num, field_num : int) return int;
1298		pragma Import(C,PQgetisnull,"PQgetisnull");
1299		C_TX :	int := int(Q.Tuple_Index) - 1;		-- Make zero based tuple #
1300		C_CX :	int := int(CX) - 1;			-- Field index
1301	begin
1302		if Q.Result = Null_Result then
1303			Raise_Exception(No_Result'Identity,
1304				"PG31: There are no query results (Is_Null).");
1305		end if;
1306		if not Is_Column(Q,CX) then
1307			Raise_Exception(No_Column'Identity,
1308				"PG32: There is now column" & Column_Index_Type'Image(CX) & " (Is_Null).");
1309		end if;
1310		return PQgetisnull(Q.Result,C_TX,C_CX) /= 0;
1311	end Is_Null;
1312
1313
1314
1315	function Value(Query : Query_Type; CX : Column_Index_Type) return String is
1316		use Interfaces.C.Strings;
1317		function PQgetvalue(R : PQ_Result; tup_num, field_num : int) return chars_ptr;
1318		pragma Import(C,PQgetvalue,"PQgetvalue");
1319		function PQgetisnull(R : PQ_Result; tup_num, field_num : int) return int;
1320		pragma Import(C,PQgetisnull,"PQgetisnull");
1321		C_TX :	int := int(Query.Tuple_Index) - 1;	-- Make zero based tuple #
1322		C_CX :	int := int(CX) - 1;			-- Field index
1323	begin
1324		if Query.Result = Null_Result then
1325			Raise_Exception(No_Result'Identity,
1326				"PG27: There are no query results (Value).");
1327		end if;
1328		if not Is_Column(Query,CX) then
1329			Raise_Exception(No_Column'Identity,
1330				"PG28: There is no column CX=" & Column_Index_Type'Image(CX) & " (Value).");
1331		end if;
1332		declare
1333			use Ada.Strings, Ada.Strings.Fixed;
1334
1335			C_Val : chars_ptr := PQgetvalue(Query.Result,C_TX,C_CX);
1336		begin
1337			if C_Val = Null_Ptr then
1338				Raise_Exception(No_Tuple'Identity,
1339					"PG29: There is no row" & Tuple_Index_Type'Image(Query.Tuple_Index) & " (Value).");
1340			elsif PQgetisnull(Query.Result,C_TX,C_CX) /= 0 then
1341				Raise_Exception(Null_Value'Identity,
1342					"PG30: Value for column" & Column_Index_Type'Image(CX) & " is NULL (Value).");
1343			else
1344				return Trim(Value_Of(C_Val),Right);
1345			end if;
1346		end;
1347
1348	end Value;
1349
1350
1351
1352	function Result(Query : Query_Type) return Natural is
1353	begin
1354		return Result_Type'Pos(Result(Query));
1355	end Result;
1356
1357
1358
1359	function Result(Query : Query_Type) return Result_Type is
1360		function PQresultStatus(R : PQ_Result) return Result_Type;
1361		pragma Import(C,PQresultStatus,"PQresultStatus");
1362	begin
1363		if Query.Result = Null_Result then
1364			Raise_Exception(No_Result'Identity,
1365				"PG13: There are no query results (function Result).");
1366		end if;
1367		return PQresultStatus(Query.Result);
1368	end Result;
1369
1370
1371
1372	function Command_Oid(Query : Query_Type) return Row_ID_Type is
1373		function PQoidValue(R : PQ_Result) return PQOid_Type;
1374		pragma Import(C,PQoidValue,"PQoidValue");
1375	begin
1376
1377		if Query.Result = Null_Result then
1378			Raise_Exception(No_Result'Identity,
1379				"PG12: There are no query results (Command_Oid).");
1380		end if;
1381
1382		return Row_ID_Type(PQoidValue(Query.Result));
1383	end Command_Oid;
1384
1385
1386
1387	function Null_Oid(Query : Query_Type) return Row_ID_Type is
1388	begin
1389		return APQ.PostgreSQL.Null_Row_ID;
1390	end Null_Oid;
1391
1392
1393
1394	function Command_Status(Query : Query_Type) return String is
1395		use Interfaces.C.Strings;
1396		function PQcmdStatus(R : PQ_Result) return chars_ptr;
1397		pragma Import(C,PQcmdStatus,"PQcmdStatus");
1398	begin
1399
1400		if Query.Result = Null_Result then
1401			Raise_Exception(No_Result'Identity,
1402				"PG11: There are no query results (Command_Status).");
1403		end if;
1404
1405		declare
1406			use Interfaces.C.Strings;
1407			Msg_Ptr : chars_ptr := PQcmdStatus(Query.Result);
1408		begin
1409			if Msg_Ptr = Null_Ptr then
1410				return "";
1411			else
1412				return Strip_NL(Value_Of(Msg_Ptr));
1413			end if;
1414		end;
1415	end Command_Status;
1416
1417
1418
1419
1420	function Error_Message(Query : Query_Type) return String is
1421		use Interfaces.C.Strings;
1422		function PQresultErrorMessage(R : PQ_Result) return chars_ptr;
1423		pragma Import(C,PQresultErrorMessage,"PQresultErrorMessage");
1424	begin
1425		if Query.Result = Null_Result then
1426			Raise_Exception(No_Result'Identity,
1427				"PG10: There are no query results (Error_Message).");
1428		end if;
1429
1430		declare
1431			use Interfaces.C.Strings;
1432			Msg_Ptr : chars_ptr := PQresultErrorMessage(Query.Result);
1433		begin
1434			if Msg_Ptr = Null_Ptr then
1435				return "";
1436			else
1437				return Strip_NL(Value_Of(Msg_Ptr));
1438			end if;
1439		end;
1440	end Error_Message;
1441
1442
1443
1444	function Is_Duplicate_Key(Query : Query_Type) return Boolean is
1445		Msg : String := Error_Message(Query);
1446		Dup : constant String := "ERROR:  Cannot insert a duplicate key";
1447	begin
1448		if Msg'Length < Dup'Length then
1449			return False;
1450		end if;
1451		return Msg(Msg'First..Msg'First+Dup'Length-1) = Dup;
1452	end Is_Duplicate_Key;
1453
1454
1455
1456	function Engine_Of(Q : Query_Type) return Database_Type is
1457	begin
1458		return Engine_PostgreSQL;
1459	end Engine_Of;
1460
1461
1462	--------------------------------------------------
1463	-- BLOB SUPPORT :
1464	--------------------------------------------------
1465
1466	function lo_creat(conn : PG_Conn; Mode : Mode_Type) return PQOid_Type;
1467	pragma Import(C,lo_creat,"lo_creat");
1468
1469	function lo_open(conn : PG_Conn; Oid : PQOid_Type; Mode : Mode_Type) return Blob_Fd;
1470	pragma Import(C,lo_open,"lo_open");
1471
1472	function lo_close(conn : PG_Conn; fd : Blob_Fd) return int;
1473	pragma Import(C,lo_close,"lo_close");
1474
1475	function lo_read(conn : PG_Conn; fd : Blob_Fd; buf : System.Address; len : size_t) return int;
1476	pragma Import(C,lo_read,"lo_read");
1477
1478	function lo_write(conn : PG_Conn; fd : Blob_Fd; buf : System.Address; len : size_t) return int;
1479	pragma Import(C,lo_write,"lo_write");
1480
1481	function lo_unlink(conn : PG_Conn; Oid : PQOid_Type) return int;
1482	pragma Import(C,lo_unlink,"lo_unlink");
1483
1484	function lo_lseek(conn : PG_Conn; fd : Blob_Fd; offset, whence : int) return int;
1485	pragma Import(C,lo_lseek,"lo_lseek");
1486
1487	procedure Free is new Ada.Unchecked_Deallocation(Blob_Object,Blob_Type);
1488
1489
1490	-- internal
1491
1492	function Raw_Index(Blob : Blob_Type) return Str.Stream_Element_Offset is
1493		use Ada.Streams;
1494		Offset : int;
1495	begin
1496		loop  -- In loop form in case EINTR processing should be required someday
1497			Offset := lo_lseek(Blob.Conn.Connection,Blob.Fd,0,Seek_Cur);
1498			exit when Offset >= 0;
1499			Raise_Exception(Blob_Error'Identity,
1500				"PG38: Server blob error occurred.");
1501		end loop;
1502
1503		return Stream_Element_Offset(Offset + 1);
1504	end Raw_Index;
1505
1506
1507
1508
1509	procedure Raw_Set_Index(Blob : Blob_Object; To : Str.Stream_Element_Offset) is
1510		Offset :	int := int(To) - 1;
1511		Z :		int;
1512	begin
1513		loop  -- In loop form in case EINTR processing should be required someday
1514			Z := lo_lseek(Blob.Conn.Connection,Blob.Fd,Offset,Seek_Set);
1515			exit when Z >= 0;
1516			Raise_Exception(Blob_Error'Identity,
1517				"PG39: Server blob error occurred.");
1518		end loop;
1519	end Raw_Set_Index;
1520
1521
1522
1523	function Internal_Size(Blob : Blob_Type) return Str.Stream_Element_Offset is
1524		use Ada.Streams;
1525		Saved_Pos :	Stream_Element_Offset := Raw_Index(Blob);
1526		End_Offset :	int := lo_lseek(Blob.Conn.Connection,Blob.Fd,0,Seek_End);
1527	begin
1528		if End_Offset < 0 then
1529			Raise_Exception(Blob_Error'Identity,
1530				"PG40: Server blob error occurred.");
1531		end if;
1532		Raw_Set_Index(Blob.all,Saved_Pos);
1533		return Stream_Element_Offset(End_Offset);
1534	end Internal_Size;
1535
1536
1537
1538	procedure Internal_Write(
1539		Stream:		in out	Blob_Object;
1540		Item:		in	Ada.Streams.Stream_Element_Array
1541	) is
1542		use Ada.Streams;
1543		Total :	size_t := 0;
1544		Len :	size_t;
1545		IX :	Stream_Element_Offset := Item'First;
1546		N :	int;
1547	begin
1548		while IX < Item'Last loop
1549			Len	:= size_t(Item'Last - IX + 1);
1550			N	:= lo_write(Stream.Conn.Connection,Stream.Fd,Item(IX)'Address,Len);
1551			if N < 0 then
1552				Raise_Exception(Blob_Error'Identity,
1553					"PG43: Server blob write error occurred.");
1554			elsif N > 0 then
1555				IX := IX + Stream_Element_Offset(N);
1556
1557				Stream.Phy_Offset := Stream.Phy_Offset + Stream_Element_Offset(N);
1558				if Stream.Phy_Offset - 1 > Stream.The_Size then
1559					Stream.The_Size := Stream.Phy_Offset - 1;
1560				end if;
1561			end if;
1562
1563			if N = 0 then
1564				Raise_Exception(Ada.IO_Exceptions.End_Error'Identity,
1565					"PG44: End_Error raised while server was writing blob.");
1566			end if;
1567		end loop;
1568
1569	end Internal_Write;
1570
1571
1572
1573	procedure Internal_Read(
1574		Stream:	in out	Blob_Object;
1575		Item:	out	Ada.Streams.Stream_Element_Array;
1576		Last:	out	Ada.Streams.Stream_Element_Offset
1577	) is
1578		use Ada.Streams;
1579
1580		Len :	size_t := size_t(Item'Length);
1581		N :	int;
1582	begin
1583
1584		loop  -- In loop form in case EINTR processing should be required someday
1585			N := lo_read(Stream.Conn.Connection,Stream.Fd,Item(Item'First)'Address,Len);
1586			exit when N >= 0;
1587			Raise_Exception(Blob_Error'Identity,
1588				"PG41: Server blob error occurred while reading the blob.");
1589		end loop;
1590
1591		if N = 0 then
1592			Raise_Exception(Ada.IO_Exceptions.End_Error'Identity,
1593				"PG42: Reached the end of blob while reading.");
1594		end if;
1595
1596		Last := Item'First + Stream_Element_Offset(N) - 1;
1597		Stream.Phy_Offset := Stream.Phy_Offset + Stream_Element_Offset(N);
1598
1599	end Internal_Read;
1600
1601
1602
1603	procedure Internal_Blob_Open(Blob : in out Blob_Type; Mode : Mode_Type; Buf_Size : Natural := Buf_Size_Default) is
1604		use Ada.Streams;
1605	begin
1606		Blob.Mode	:= Internal_Blob_Open.Mode;
1607		Blob.Fd		:= lo_open(Blob.Conn.Connection,PQOid_Type(Blob.Oid),Blob.Mode);
1608		if Blob.Fd = -1 then
1609			Free(Blob);
1610			Raise_Exception(Blob_Error'Identity,
1611				"PG45: Unable to open blob on server (OID=" & Row_ID_Type'Image(Blob.Oid) & ").");
1612		end if;
1613		if Buf_Size > 0 then
1614			Blob.Buffer	:= new Stream_Element_Array(1..Stream_Element_Offset(Buf_Size));
1615			Blob.Buf_Empty	:= True;
1616			Blob.Buf_Dirty	:= False;
1617			Blob.Buf_Offset	:= 0;
1618			Blob.Log_Offset	:= 1;
1619			Blob.Phy_Offset	:= 1;
1620			Blob.The_Size	:= Stream_Element_Offset(Internal_Size(Blob));
1621		else
1622			null;		-- unbuffered blob operations will be used
1623		end if;
1624	end Internal_Blob_Open;
1625
1626
1627
1628	procedure Internal_Set_Index(Blob : in out Blob_Object; To : Str.Stream_Element_Offset) is
1629		use Ada.Streams;
1630	begin
1631		if Blob.Phy_Offset /= Stream_Element_Offset(To) then
1632			Raw_Set_Index(Blob,To);
1633			Blob.Phy_Offset := Stream_Element_Offset(To);
1634		end if;
1635	end Internal_Set_Index;
1636
1637
1638
1639	-- end internal
1640
1641
1642
1643	function Blob_Create(DB : access Connection_Type; Buf_Size : Natural := Buf_Size_Default) return Blob_Type is
1644		Blob : Blob_Type;
1645	begin
1646		Blob := new Blob_Object(DB);
1647		Blob.Oid := Row_ID_Type(lo_creat(Blob.Conn.Connection,Read_Write));
1648		if Blob.Oid = -1 then
1649			free(Blob);
1650			Raise_Exception(Blob_Error'Identity,
1651				"PG46: Unable to create blob on server.");
1652		end if;
1653
1654		begin
1655			Internal_Blob_Open(Blob,Write,Buf_Size);
1656		exception
1657			when Ex : others =>
1658				Blob_Unlink(DB.all,Blob.Oid);	-- Release what will result in an unused blob!
1659				Reraise_Occurrence(Ex);		-- HINT: Internal_Blob_Open() FAILS IF IT IS NOT IN A TRANSACTION!
1660		end;
1661
1662		return Blob;
1663	end Blob_Create;
1664
1665
1666
1667	function Blob_Open(DB : access Connection_Type; Oid : Row_ID_Type; Mode : Mode_Type; Buf_Size : Natural := Buf_Size_Default) return Blob_Type is
1668		Blob : Blob_Type;
1669	begin
1670		Blob		:= new Blob_Object(DB);
1671		Blob.Oid	:= Blob_Open.Oid;
1672		Internal_Blob_Open(Blob,Mode,Buf_Size);
1673		return Blob;
1674	end Blob_Open;
1675
1676
1677
1678	procedure Blob_Flush(Blob : in out Blob_Object) is
1679	begin
1680		if Blob.Buffer /= null then
1681			if ( not Blob.Buf_Empty ) and Blob.Buf_Dirty then
1682				Internal_Set_Index(Blob,Blob.Buf_Offset);
1683				Internal_Write(Blob,Blob.Buffer(1..Blob.Buf_Size));
1684			end if;
1685			Blob.Buf_Dirty := False;
1686		else
1687			null;				-- Ignore flush calls in the unbuffered case
1688		end if;
1689	end Blob_Flush;
1690
1691
1692
1693	procedure Blob_Flush(Blob : Blob_Type) is
1694	begin
1695		Blob_Flush(Blob.all);
1696	end Blob_Flush;
1697
1698
1699
1700	procedure Internal_Blob_Close(Blob : in out Blob_Object) is
1701		Z : int;
1702	begin
1703		if Blob.Buffer /= null then
1704			if Blob.Buf_Dirty then
1705				Blob_Flush(Blob);
1706			end if;
1707			Free(Blob.Buffer);
1708		end if;
1709
1710		Z := lo_close(Blob.Conn.Connection,Blob.Fd);
1711		if Z /= 0 then
1712			Raise_Exception(Blob_Error'Identity,
1713				"PG47: Server error when closing blob.");
1714		end if;
1715		Blob.Fd := -1;
1716	end Internal_Blob_Close;
1717
1718
1719
1720	procedure Blob_Close(Blob : in out Blob_Type) is
1721	begin
1722		Internal_Blob_Close(Blob.all);
1723		Free(Blob);
1724	end Blob_Close;
1725
1726
1727
1728	procedure Blob_Set_Index(Blob : Blob_Type; To : Blob_Offset) is
1729		use Ada.Streams;
1730	begin
1731		if Blob.Buffer /= null then
1732			Blob.Log_Offset := Stream_Element_Offset(To);
1733		else
1734			Internal_Set_Index(Blob.all,Stream_Element_Offset(To));
1735		end if;
1736	end Blob_Set_Index;
1737
1738
1739
1740	function Internal_Index(Blob : Blob_Type) return Str.Stream_Element_Offset is
1741	begin
1742		return Blob.Phy_Offset;
1743	end Internal_Index;
1744
1745
1746
1747	function Blob_Index(Blob : Blob_Type) return Blob_Offset is
1748	begin
1749		if Blob.Buffer /= null then
1750			return Blob_Offset(Blob.Log_Offset);
1751		else
1752			return Blob_Offset(Internal_Index(Blob));
1753		end if;
1754	end Blob_Index;
1755
1756
1757
1758	function End_of_Blob(Blob : Blob_Type) return Boolean is
1759		use Ada.Streams;
1760	begin
1761		if Blob.Buffer /= null then
1762			return Blob.Log_Offset > Blob.The_Size;
1763		else
1764			return Blob_Index(Blob) > Blob_Size(Blob);
1765		end if;
1766	end End_of_Blob;
1767
1768
1769
1770	function Blob_Oid(Blob : Blob_Type) return Row_ID_Type is
1771	begin
1772		return Blob.Oid;
1773	end Blob_Oid;
1774
1775
1776
1777	function Blob_Size(Blob : Blob_Type) return Blob_Count is
1778	begin
1779		if Blob.Buffer /= null then
1780			return Blob_Count(Blob.The_Size);
1781		else
1782			return Blob_Count(Internal_Size(Blob));
1783		end if;
1784	end Blob_Size;
1785
1786
1787
1788	function Blob_Stream(Blob : Blob_Type) return Root_Stream_Access is
1789	begin
1790		if Blob = Null then
1791			Raise_Exception(Blob_Error'Identity,
1792				"PG49: No blob to create a stream from (Blob_Stream).");
1793		end if;
1794		return Root_Stream_Access(Blob);
1795	end Blob_Stream;
1796
1797
1798
1799	procedure Blob_Unlink(DB : Connection_Type; Oid : Row_ID_Type) is
1800		Z : int;
1801	begin
1802		Z := lo_unlink(DB.Connection,PQOid_Type(Oid));
1803		if Z = -1 then
1804			Raise_Exception(Blob_Error'Identity,
1805				"PG50: Unable to unlink blob OID=" & Row_ID_Type'Image(Oid) & " (Blob_Unlink).");
1806		end if;
1807	end Blob_Unlink;
1808
1809
1810
1811	function lo_import(conn : PG_Conn; filename : System.Address) return int;
1812	pragma Import(C,lo_import,"lo_import");
1813
1814	function lo_export(conn : PG_Conn; Oid : PQOid_Type; filename : System.Address) return int;
1815	pragma Import(C,lo_export,"lo_export");
1816
1817
1818	procedure Blob_Import(DB : Connection_Type; Pathname : String; Oid : out Row_ID_Type) is
1819		use Interfaces.C;
1820		P : char_array := To_C(Pathname);
1821		Z : int;
1822	begin
1823		Oid := Row_ID_Type'Last;
1824		Z := lo_import(DB.Connection,P'Address);
1825		if Z <= -1 then
1826			Raise_Exception(Blob_Error'Identity,
1827				"PG51: Unable to import blob from " & Pathname & " (Blob_Import).");
1828		end if;
1829		Oid := Row_ID_Type(Z);
1830	end Blob_Import;
1831
1832
1833
1834	procedure Blob_Export(DB : Connection_Type; Oid : Row_ID_Type; Pathname : String) is
1835		P : char_array := To_C(Pathname);
1836		Z : int;
1837	begin
1838		Z := lo_export(DB.Connection,PQOid_Type(Oid),P'Address);
1839		if Z <= -1 then
1840			Raise_Exception(Blob_Error'Identity,
1841				"PG52: Unable to export blob to " & Pathname & " (Blob_Export).");
1842		end if;
1843	end Blob_Export;
1844
1845
1846
1847	function Generic_Blob_Open(DB : access Connection_Type; Oid : Oid_Type; Mode : Mode_Type; Buf_Size : Natural := Buf_Size_Default) return Blob_Type is
1848	begin
1849		return Blob_Open(DB,Row_ID_Type(Oid),Mode,Buf_Size);
1850	end Generic_Blob_Open;
1851
1852
1853
1854	function Generic_Blob_Oid(Blob : Blob_Type) return Oid_Type is
1855	begin
1856		return Oid_Type(Blob_Oid(Blob));
1857	end Generic_Blob_Oid;
1858
1859
1860
1861	procedure Generic_Blob_Unlink(DB : Connection_Type; Oid : Oid_Type) is
1862	begin
1863		Blob_Unlink(DB,Row_ID_Type(Oid));
1864	end Generic_Blob_Unlink;
1865
1866
1867
1868	procedure Generic_Blob_Import(DB : Connection_Type; Pathname : String; Oid : out Oid_Type) is
1869		Local_Oid : Row_ID_Type;
1870	begin
1871		Blob_Import(DB,Pathname,Local_Oid);
1872		Oid := Oid_Type(Local_Oid);
1873	end Generic_Blob_Import;
1874
1875
1876
1877	procedure Generic_Blob_Export(DB : Connection_Type; Oid : Oid_Type; Pathname : String) is
1878	begin
1879		Blob_Export(DB,Row_ID_Type(Oid),Pathname);
1880	end Generic_Blob_Export;
1881
1882
1883
1884-- private
1885
1886
1887	---------------------
1888	-- CONNECTION_TYPE --
1889	---------------------
1890
1891
1892   procedure Initialize(C : in out Connection_Type) is
1893   begin
1894      C.Port_Format := IP_Port;
1895      C.Port_Number := 5432;
1896      C.keyname_val_cache_uptodate := false;
1897
1898   end Initialize;
1899
1900
1901
1902	procedure Finalize(C : in out Connection_Type) is
1903	begin
1904		Internal_Reset(C,In_Finalize => True);
1905	end Finalize;
1906
1907
1908
1909	function Internal_Connection(C : Connection_Type) return PG_Conn is
1910	begin
1911		return C.Connection;
1912	end Internal_Connection;
1913
1914
1915
1916	function Query_Factory( C: in Connection_Type ) return Root_Query_Type'Class is
1917		q: Query_Type;
1918	begin
1919		return q;
1920	end query_factory;
1921
1922
1923
1924	----------------
1925	-- QUERY_TYPE --
1926	----------------
1927
1928
1929	procedure Adjust(Q : in out Query_Type) is
1930	begin
1931		Q.Result := Null_Result;
1932		Adjust(Root_Query_Type(Q));
1933	end Adjust;
1934
1935
1936
1937	procedure Finalize(Q : in out Query_Type) is
1938	begin
1939		Clear(Q);
1940	end Finalize;
1941
1942
1943
1944 	function SQL_Code(Query : Query_Type) return SQL_Code_Type is
1945	begin
1946		return 0;
1947	end SQL_Code;
1948
1949
1950
1951	---------------
1952	-- BLOB_TYPE --
1953	---------------
1954
1955
1956	procedure Finalize(Blob : in out Blob_Object) is
1957	begin
1958		if Blob.Fd /= -1 then
1959			Internal_Blob_Close(Blob);
1960		end if;
1961	end Finalize;
1962
1963
1964
1965	procedure Read(
1966		Stream:	in out	Blob_Object;
1967		Item:	out	Ada.Streams.Stream_Element_Array;
1968		Last:	out	Ada.Streams.Stream_Element_Offset
1969	) is
1970		use Ada.Streams;
1971
1972		IX : Stream_Element_Offset := Item'First;
1973		BX : Stream_Element_Offset;
1974	begin
1975
1976		if Stream.Buffer /= null then
1977			while IX <= Item'Last and Stream.Log_Offset <= Stream.The_Size loop
1978
1979				if ( not Stream.Buf_Empty ) and then Stream.Buf_Dirty then	-- if not empty and is dirty
1980					if Stream.Log_Offset < Stream.Buf_Offset		-- if offset too low
1981					or else Stream.Log_Offset >= Stream.Buf_Offset + Stream.Buf_Size then	-- or offset too high
1982						Blob_Flush(Stream);
1983						Stream.Buf_Empty := True;
1984					end if;
1985				end if;
1986
1987				if Stream.Buf_Empty then					-- If we have an empty buffer then..
1988					if Stream.Log_Offset > Stream.The_Size + 1 then
1989						Raise_Exception(Ada.IO_Exceptions.End_Error'Identity,
1990							"PG47: End reached while reading blob.");
1991					end if;
1992
1993					Stream.Buf_Offset := Stream.Log_Offset;			-- Start with our convenient offset
1994					Stream.Buf_Size	:= Stream.Buffer.all'Length;		-- Try to read entire buffer in
1995					if Stream.Buf_Offset + Stream.Buf_Size - 1 > Stream.The_Size then
1996						Stream.Buf_Size := Stream.The_Size + 1 - Stream.Buf_Offset;  -- read somewhat less in
1997					end if;
1998					Internal_Set_Index(Stream,Stream.Buf_Offset);
1999					Internal_Read(Stream,Stream.Buffer(1..Stream.Buf_Size),Last);
2000					if Last /= Stream.Buf_Size then				-- Check that all was read
2001						Raise_Exception(Blob_Error'Identity,
2002							"PG48: Error while reading from blob.");
2003					end if;
2004					Stream.Buf_Empty := False;				-- Buffer is not empty
2005					pragma assert(Stream.Buf_Dirty = False);		-- Should not be dirty at this point
2006					BX := Stream.Buffer.all'First;				-- Start reading from buffer here
2007				else
2008					BX := Stream.Log_Offset - Stream.Buf_Offset + Stream.Buffer.all'First;
2009				end if;
2010
2011				Item(IX)		:= Stream.Buffer.all(BX);		-- Read item byte
2012				IX			:= IX + 1;				-- Advance item index
2013				Stream.Log_Offset	:= Stream.Log_Offset + 1;		-- Advance logical offset
2014			end loop;
2015			Last := IX - 1;
2016		else
2017			Internal_Read(Stream,Item,Last);
2018		end if;
2019	end Read;
2020
2021
2022
2023	procedure Write(
2024		Stream:	in out	Blob_Object;
2025		Item:	in	Ada.Streams.Stream_Element_Array
2026	) is
2027		use Ada.Streams;
2028
2029		IX : Stream_Element_Offset := Item'First;
2030		BX : Stream_Element_Offset := -1;
2031	begin
2032
2033		if Stream.Buffer /= null then
2034			while IX <= Item'Last loop
2035				if ( not Stream.Buf_Empty ) and then Stream.Buf_Dirty then			-- Buffer is not empty and is dirty
2036					if		Stream.Log_Offset <  Stream.Buf_Offset			-- if offset too low
2037					or else Stream.Log_Offset >  Stream.Buf_Offset + Stream.Buf_Size	-- or offset too high
2038					or else Stream.Buf_Size	>= Stream.Buffer.all'Length then		-- or buffer is full then..
2039						Blob_Flush(Stream);						-- Flush out dirty data
2040						Stream.Buf_Empty := True;					-- Now mark buffer as empty
2041					else
2042						BX := Stream.Log_Offset - Stream.Buf_Offset + Stream.Buffer.all'First;
2043					end if;
2044				else
2045					BX := Stream.Log_Offset - Stream.Buf_Offset + Stream.Buffer.all'First;
2046				end if;
2047
2048				if Stream.Buf_Empty then					-- if buf was empty or was just made empty then..
2049					Stream.Buf_Offset	:= Stream.Log_Offset;		-- Set to our convenient offset
2050					Stream.Buf_Size		:= 0;				-- No data in this buffer yet
2051					Stream.Buf_Dirty	:= False;			-- Make sure it's not marked dirty yet
2052					BX			:= Stream.Buffer.all'First;	-- Point to start of buffer
2053				end if;
2054
2055				Stream.Buffer.all(BX)	:= Item(IX);				-- Write the byte
2056				IX			:= IX + 1;				-- Advance Item Index
2057				Stream.Log_Offset	:= Stream.Log_Offset + 1;		-- Advance the logical blob offset
2058				Stream.Buf_Empty	:= False;				-- Buffer is no longer empty
2059				Stream.Buf_Dirty	:= True;				-- Buffer has been modified
2060
2061				if BX > Stream.Buf_Size then					-- Did the buffer contents grow?
2062					Stream.Buf_Size	 := Stream.Buf_Size + 1;		-- Buffer size has grown
2063				end if;
2064			end loop;
2065		else
2066			Internal_Write(Stream,Item);
2067		end if;
2068	end Write;
2069
2070
2071begin
2072
2073	declare
2074		use Ada.Calendar;
2075	begin
2076		No_Date := Time_Of(Year_Number'First,Month_Number'First,Day_Number'First);
2077	end;
2078
2079end APQ.PostgreSQL.Client;
2080
2081-- End $Source: /cvsroot/apq/apq/apq-postgresql-client.adb,v $
2082