<commit_before>-----------------------------------------------------------------------
--  ado-statements-sqlite -- SQLite database statements
--  Copyright (C) 2009 - 2021 <NAME>
--  Written by <NAME> (<EMAIL>)
--
--  Licensed under the Apache License, Version 2.0 (the "License");
--  you may not use this file except in compliance with the License.
--  You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
--  Unless required by applicable law or agreed to in writing, software
--  distributed under the License is distributed on an "AS IS" BASIS,
--  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--  See the License for the specific language governing permissions and
--  limitations under the License.
-----------------------------------------------------------------------
with Sqlite3_H.Perfect_Hash;

with Interfaces.C.Strings;

with Util.Log;
with Util.Log.Loggers;

with ADO.Sessions;
with ADO.Dialects;
package body ADO.Statements.Sqlite is

   use type ADO.Schemas.Class_Mapping_Access;
   use type ADO.Schemas.Column_Index;

   Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("ADO.Statements.Sqlite");

   type Dialect is new ADO.Dialects.Dialect with null record;

   --  Check if the string is a reserved keyword.
   overriding
   function Is_Reserved (D    : in Dialect;
                         Name : in String) return Boolean;

   --  Get the quote character to escape an identifier.
   overriding
   function Get_Identifier_Quote (D : in Dialect) return Character;

   --  Append the item in the buffer escaping some characters if necessary
   overriding
   procedure Escape_Sql (D      : in Dialect;
                         Buffer : in out Unbounded_String;
                         Item   : in ADO.Blob_Ref);

   Sqlite_Dialect : aliased Dialect;

   function Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                     SQL        : in String) return access Sqlite3_H.sqlite3_stmt;

   --  Releases the sqlite statement
   procedure Release_Stmt (Connection : access ADO.Connections.Sqlite.Sqlite3;
                           Stmt       : access Sqlite3_H.sqlite3_stmt);

   procedure Prepare (Stmt  : in out Sqlite_Query_Statement;
                      Query : in String);

   --  ------------------------------
   --  Check if the string is a reserved keyword.
   --  ------------------------------
   overriding
   function Is_Reserved (D    : in Dialect;
                         Name : in String) return Boolean is
      pragma Unreferenced (D);
   begin
      return Sqlite3_H.Perfect_Hash.Is_Keyword (Name);
   end Is_Reserved;

   --  ------------------------------
   --  Get the quote character to escape an identifier.
   --  ------------------------------
   overriding
   function Get_Identifier_Quote (D : in Dialect) return Character is
      pragma Unreferenced (D);
   begin
      return '`';
   end Get_Identifier_Quote;

   --  ------------------------------
   --  Append the item in the buffer escaping some characters if necessary
   --  ------------------------------
   overriding
   procedure Escape_Sql (D      : in Dialect;
                         Buffer : in out Unbounded_String;
                         Item   : in ADO.Blob_Ref) is
      pragma Unreferenced (D);

      C    : Ada.Streams.Stream_Element;
      Blob : constant ADO.Blob_Accessor := Item.Value;
   begin
      Append (Buffer, ''');
      for I in Blob.Data'Range loop
         C := Blob.Data (I);
         case C is
            when Character'Pos (ASCII.NUL) =>
               Append (Buffer, '\');
               Append (Buffer, '0');

            when Character'Pos (''') =>
               Append (Buffer, ''');
               Append (Buffer, Character'Val (C));

            when others =>
               Append (Buffer, Character'Val (C));
         end case;
      end loop;
      Append (Buffer, ''');
   end Escape_Sql;

   --  ------------------------------
   --  Check for an error after executing a sqlite statement.
   --  ------------------------------
   procedure Check_Error (Connection : access ADO.Connections.Sqlite.Sqlite3;
                          SQL        : in String;
                          Result     : in int) is
   begin
      if Result /= Sqlite3_H.SQLITE_OK and Result /= Sqlite3_H.SQLITE_DONE then
         declare
            Error : constant Strings.chars_ptr := Sqlite3_H.sqlite3_errmsg (Connection);
            Msg   : constant String := Strings.Value (Error);
         begin
            if SQL'Length > 0 then
               Log.Error ("Query failed: '{0}'", SQL);
            end if;
            Log.Error ("Error {0}: {1}", int'Image (Result), Msg);
            raise ADO.Statements.SQL_Error with "SQL error: " & Msg;
         end;
      end if;
   end Check_Error;

   function Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                     SQL        : in String) return access Sqlite3_H.sqlite3_stmt is

      function sqlite3_prepare_v2 (db : access Sqlite3_H.sqlite3;
                                   zSql : String;
                                   nByte : int;
                                   ppStmt : System.Address;
                                   pzTail : System.Address) return int;
      pragma Import (C, sqlite3_prepare_v2, "sqlite3_prepare_v2");

      Handle : aliased access Sqlite3_H.sqlite3_stmt;
      Result : int;
   begin
      Log.Debug ("Execute: {0}", SQL);

      Result := sqlite3_prepare_v2 (db     => Connection,
                                    zSql   => SQL,
                                    nByte  => int (SQL'Length),
                                    ppStmt => Handle'Address,
                                    pzTail => System.Null_Address);
      if Result /= Sqlite3_H.SQLITE_OK then
         Check_Error (Connection, SQL, Result);
      end if;

      return Handle;
   end Execute;

   --  ------------------------------
   --  Execute SQL statement.
   --  ------------------------------
   procedure Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                      SQL        : in String) is
      Handle    : access Sqlite3_H.sqlite3_stmt;
      Res       : int;
   begin
      Handle := Execute (Connection => Connection,
                         SQL        => SQL);

      Res := Sqlite3_H.sqlite3_step (Handle);
      Release_Stmt (Connection, Handle);
      Check_Error (Connection, SQL, Res);
   end Execute;

   --  ------------------------------
   --  Releases the sqlite statement
   --  ------------------------------
   procedure Release_Stmt (Connection : access ADO.Connections.Sqlite.Sqlite3;
                           Stmt       : access Sqlite3_H.sqlite3_stmt) is
      Result : int;
   begin
      Result := Sqlite3_H.sqlite3_finalize (Stmt);
      Check_Error (Connection, "", Result);
   end Release_Stmt;

   --  ------------------------------
   --  Delete statement
   --  ------------------------------

   --  ------------------------------
   --  Execute the query
   --  ------------------------------
   overriding
   procedure Execute (Stmt   : in out Sqlite_Delete_Statement;
                      Result : out Natural) is
   begin
      if Stmt.Connection = null then
         raise ADO.Sessions.Session_Error with "Database connection is closed";
      end if;<commit_msg>Fix #5: don't call Check_Error to check the result of sqlite3_finalize<commit_after>-----------------------------------------------------------------------
--  ado-statements-sqlite -- SQLite database statements
--  Copyright (C) 2009 - 2021 <NAME>
--  Written by <NAME> (<EMAIL>)
--
--  Licensed under the Apache License, Version 2.0 (the "License");
--  you may not use this file except in compliance with the License.
--  You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
--  Unless required by applicable law or agreed to in writing, software
--  distributed under the License is distributed on an "AS IS" BASIS,
--  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--  See the License for the specific language governing permissions and
--  limitations under the License.
-----------------------------------------------------------------------
with Sqlite3_H.Perfect_Hash;

with Interfaces.C.Strings;

with Util.Log;
with Util.Log.Loggers;

with ADO.Sessions;
with ADO.Dialects;
package body ADO.Statements.Sqlite is

   use type ADO.Schemas.Class_Mapping_Access;
   use type ADO.Schemas.Column_Index;

   Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("ADO.Statements.Sqlite");

   type Dialect is new ADO.Dialects.Dialect with null record;

   --  Check if the string is a reserved keyword.
   overriding
   function Is_Reserved (D    : in Dialect;
                         Name : in String) return Boolean;

   --  Get the quote character to escape an identifier.
   overriding
   function Get_Identifier_Quote (D : in Dialect) return Character;

   --  Append the item in the buffer escaping some characters if necessary
   overriding
   procedure Escape_Sql (D      : in Dialect;
                         Buffer : in out Unbounded_String;
                         Item   : in ADO.Blob_Ref);

   Sqlite_Dialect : aliased Dialect;

   function Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                     SQL        : in String) return access Sqlite3_H.sqlite3_stmt;

   --  Releases the sqlite statement
   procedure Release_Stmt (Connection : access ADO.Connections.Sqlite.Sqlite3;
                           Stmt       : access Sqlite3_H.sqlite3_stmt);

   procedure Prepare (Stmt  : in out Sqlite_Query_Statement;
                      Query : in String);

   --  ------------------------------
   --  Check if the string is a reserved keyword.
   --  ------------------------------
   overriding
   function Is_Reserved (D    : in Dialect;
                         Name : in String) return Boolean is
      pragma Unreferenced (D);
   begin
      return Sqlite3_H.Perfect_Hash.Is_Keyword (Name);
   end Is_Reserved;

   --  ------------------------------
   --  Get the quote character to escape an identifier.
   --  ------------------------------
   overriding
   function Get_Identifier_Quote (D : in Dialect) return Character is
      pragma Unreferenced (D);
   begin
      return '`';
   end Get_Identifier_Quote;

   --  ------------------------------
   --  Append the item in the buffer escaping some characters if necessary
   --  ------------------------------
   overriding
   procedure Escape_Sql (D      : in Dialect;
                         Buffer : in out Unbounded_String;
                         Item   : in ADO.Blob_Ref) is
      pragma Unreferenced (D);

      C    : Ada.Streams.Stream_Element;
      Blob : constant ADO.Blob_Accessor := Item.Value;
   begin
      Append (Buffer, ''');
      for I in Blob.Data'Range loop
         C := Blob.Data (I);
         case C is
            when Character'Pos (ASCII.NUL) =>
               Append (Buffer, '\');
               Append (Buffer, '0');

            when Character'Pos (''') =>
               Append (Buffer, ''');
               Append (Buffer, Character'Val (C));

            when others =>
               Append (Buffer, Character'Val (C));
         end case;
      end loop;
      Append (Buffer, ''');
   end Escape_Sql;

   --  ------------------------------
   --  Check for an error after executing a sqlite statement.
   --  ------------------------------
   procedure Check_Error (Connection : access ADO.Connections.Sqlite.Sqlite3;
                          SQL        : in String;
                          Result     : in int) is
   begin
      if Result /= Sqlite3_H.SQLITE_OK and Result /= Sqlite3_H.SQLITE_DONE then
         declare
            Error : constant Strings.chars_ptr := Sqlite3_H.sqlite3_errmsg (Connection);
            Msg   : constant String := Strings.Value (Error);
         begin
            if SQL'Length > 0 then
               Log.Error ("Query failed: '{0}'", SQL);
            end if;
            Log.Error ("Error {0}: {1}", int'Image (Result), Msg);
            raise ADO.Statements.SQL_Error with "SQL error: " & Msg;
         end;
      end if;
   end Check_Error;

   function Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                     SQL        : in String) return access Sqlite3_H.sqlite3_stmt is

      function sqlite3_prepare_v2 (db : access Sqlite3_H.sqlite3;
                                   zSql : String;
                                   nByte : int;
                                   ppStmt : System.Address;
                                   pzTail : System.Address) return int;
      pragma Import (C, sqlite3_prepare_v2, "sqlite3_prepare_v2");

      Handle : aliased access Sqlite3_H.sqlite3_stmt;
      Result : int;
   begin
      Log.Debug ("Execute: {0}", SQL);

      Result := sqlite3_prepare_v2 (db     => Connection,
                                    zSql   => SQL,
                                    nByte  => int (SQL'Length),
                                    ppStmt => Handle'Address,
                                    pzTail => System.Null_Address);
      if Result /= Sqlite3_H.SQLITE_OK then
         Check_Error (Connection, SQL, Result);
      end if;

      return Handle;
   end Execute;

   --  ------------------------------
   --  Execute SQL statement.
   --  ------------------------------
   procedure Execute (Connection : access ADO.Connections.Sqlite.Sqlite3;
                      SQL        : in String) is
      Handle    : access Sqlite3_H.sqlite3_stmt;
      Res       : int;
   begin
      Handle := Execute (Connection => Connection,
                         SQL        => SQL);

      Res := Sqlite3_H.sqlite3_step (Handle);
      Release_Stmt (Connection, Handle);
      Check_Error (Connection, SQL, Res);
   end Execute;

   --  ------------------------------
   --  Releases the sqlite statement
   --  ------------------------------
   procedure Release_Stmt (Connection : access ADO.Connections.Sqlite.Sqlite3;
                           Stmt       : access Sqlite3_H.sqlite3_stmt) is
      Result : int;
      pragma Unreferenced (Result);
   begin
      Result := Sqlite3_H.sqlite3_finalize (Stmt);
   end Release_Stmt;

   --  ------------------------------
   --  Delete statement
   --  ------------------------------

   --  ------------------------------
   --  Execute the query
   --  ------------------------------
   overriding
   procedure Execute (Stmt   : in out Sqlite_Delete_Statement;
                      Result : out Natural) is
   begin
      if Stmt.Connection = null then
         raise ADO.Sessions.Session_Error with "Database connection is closed";
      end if;
{'fraction_non_alphanumeric': 0.16132081842123033, 'fraction_numerical': 0.00783307448173408, 'mean_word_length': 1.4223094537127903, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4423800', 'n_tokens_mistral': 4021, 'n_tokens_neox': 3866, 'n_words': 1173}