------------------------------------------------------------------------------
-- Database Package Parser (MySQL Multiple Connection support)              --
--                                                                          --
-- Part of SparForte                                                        --
------------------------------------------------------------------------------
--                                                                          --
--            Copyright (C) 2001-2024 Free Software Foundation              --
--                                                                          --
-- This is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
-- sion.  This is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with this;  see file COPYING.  If not, write --
-- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
-- MA 02111-1307, USA.                                                      --
--                                                                          --
-- This is maintained at http://www.pegasoft.ca                             --
--                                                                          --
------------------------------------------------------------------------------

with ada.io_exceptions,
     ada.strings.unbounded,
     --ada.text_io,
#if MYSQL
     APQ.MySQL.Client,
     pegasoft.mysql,
#end if;
     spar_os.tty,
     world.utf8,
     signal_flags,
     pegasoft.strings,
     pegasoft.user_io,
     scanner,
     scanner.communications,
     scanner_res,
     parser_params,
     parser_db;
use  ada.strings.unbounded,
     --ada.text_io,
#if MYSQL
     APQ,
     APQ.MySQL,
     APQ.MySQL.Client,
     pegasoft.mysql,
#end if;
     spar_os.tty,
     world,
     world.utf8,
     signal_flags,
     pegasoft,
     pegasoft.strings,
     pegasoft.user_io,
     scanner,
     scanner.communications,
     scanner_res,
     parser_params,
     parser_db;

package body parser_mysqlm is

------------------------------------------------------------------------------
-- Database package identifiers
------------------------------------------------------------------------------

mysqlm_connection_t : identifier;
mysqlm_query_t : identifier;

mysqlm_column_index_type_t : identifier;
mysqlm_tuple_index_type_t : identifier;
mysqlm_tuple_count_type_t : identifier;

mysqlm_mode_type_t       : identifier;
mysqlm_read_t            : identifier;
mysqlm_write_t           : identifier;
mysqlm_read_write_t      : identifier;

mysqlm_new_connection_t : identifier;
mysqlm_new_query_t : identifier;
mysqlm_connect_t      : identifier;
mysqlm_disconnect_t   : identifier;
mysqlm_is_connected_t : identifier;
mysqlm_reset_t        : identifier;
mysqlm_error_message_t: identifier;
--mysqlm_notice_message_t: identifier;
mysqlm_in_abort_state_t: identifier;
mysqlm_options_t      : identifier;
mysqlm_will_rollback_on_finalize_t : identifier;
mysqlm_set_rollback_on_finalize_t : identifier;
mysqlm_open_db_trace_t: identifier;
mysqlm_close_db_trace_t: identifier;
mysqlm_set_trace_t    : identifier;
mysqlm_is_trace_t     : identifier;
mysqlm_clear_t        : identifier;
mysqlm_prepare_t      : identifier;
mysqlm_append_t       : identifier;
mysqlm_append_line_t  : identifier;
mysqlm_append_quoted_t: identifier;
mysqlm_execute_t      : identifier;
mysqlm_execute_checked_t : identifier;
mysqlm_raise_exceptions_t : identifier;
mysqlm_report_errors_t : identifier;
mysqlm_begin_work_t   : identifier;
mysqlm_commit_work_t  : identifier;
mysqlm_rollback_work_t : identifier;
mysqlm_rewind_t : identifier;
mysqlm_fetch_t : identifier;
mysqlm_end_of_query_t : identifier;
mysqlm_tuple_t        : identifier;
mysqlm_tuples_t       : identifier;
mysqlm_columns_t      : identifier;
mysqlm_column_name_t  : identifier;
mysqlm_column_index_t : identifier;
--mysqlm_column_type_t  : identifier;
mysqlm_is_null_t      : identifier;
mysqlm_value_t        : identifier;
mysqlm_engine_of_t    : identifier;
mysqlm_show_t         : identifier;
mysqlm_list_t         : identifier;
mysqlm_schema_t       : identifier;
mysqlm_users_t        : identifier;
mysqlm_databases_t    : identifier;
mysqlm_fetch_values_t : identifier;
mysqlm_append_for_insert_t : identifier;
mysqlm_append_for_update_t : identifier;

#if MYSQL

-- Utilities
--
-- findConnection and findQuery are to avoid throwing exceptions
-- on uninitialized resources, giving a meaningful error instead.

procedure findConnection( connectId : identifier; connection : in out resPtr ) is
  expr : constant unbounded_string := identifiers( connectId ).value.all;
begin
  if length( expr ) = 0 then
     err( em( "new_connnection" ) & pl( " has not been called to initialize the connection" ) );
  else
     findResource( to_resource_id( expr ), connection );
  end if;
end findConnection;

procedure findQuery( queryId : identifier; query : in out resPtr ) is
  expr : constant unbounded_string := identifiers( queryId ).value.all;
begin
  if length( expr ) = 0 then
     err( em( "new_query" ) & pl( " has not been called to initialize the query" ) );
  else
     findResource( to_resource_id( expr ), query );
  end if;
end findQuery;

procedure ParseMySQLMConnect is
  -- Syntax: mysqlm.connect( C, dbname [,user ,passwd [,host [,port ] ] ] );
  connectId   : identifier;
  dbnameExpr : unbounded_string;
  dbnameType : identifier;
  userExpr : unbounded_string;
  userType : identifier;
  hasUser  : boolean := false;
  pswdExpr : unbounded_string;
  pswdType : identifier;
  hostExpr : unbounded_string;
  hostType : identifier;
  hasHost  : boolean := false;
  portExpr : unbounded_string;
  portType : identifier;
  hasPort  : boolean := false;
  connection : resPtr;
begin
  expect( mysqlm_connect_t );
  ParseFirstInOutParameter( mysqlm_connect_t, connectId, mysqlm_connection_t );
  ParseNextStringParameter( mysqlm_connect_t, dbnameExpr, dbnameType, string_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_connect_t, userExpr, userType, string_t );
     hasUser := true;
     if token = symbol_t and identifiers( token ).value.all = "," then
        ParseNextStringParameter( mysqlm_connect_t, pswdExpr, pswdType, string_t );
        if token = symbol_t and identifiers( token ).value.all = "," then
           ParseNextStringParameter( mysqlm_connect_t, hostExpr, hostType, string_t );
           hasHost := true;
           if token = symbol_t and identifiers( token ).value.all = "," then
              ParseNextNumericParameter( mysqlm_connect_t, portExpr, portType, integer_t );
              hasPort := true;
           end if;
        end if;
     end if;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     if restriction_no_mysql_database then
        err( +"not allowed with " & em( "pragma restriction( no_mysql_database )" ) );
     end if;
     begin
       findConnection( connectId, connection );
       begin
         Set_DB_Name( connection.C, to_string( dbnameExpr ) );
       exception when others =>
         err( pl( "Internal error: set_db_name " ) &
              em_value( dbnameExpr ) &
              pl( " failed" )
         );
       end;
       if hasUser then
          begin
            Set_User_Password( connection.C, to_string( userExpr ), to_string( pswdExpr ) );
          exception when others =>
             err( pl( "Internal error: set_user_password " ) &
                  em_value( userExpr ) &
                  pl( "/" ) &
                  em_value( pswdExpr ) &
                  pl( " failed" )
             );
          end;
       end if;
       if hasHost then
          begin
             Set_Host_Name( connection.C, to_string( hostExpr ) );
          exception when others =>
             err( pl( "Internal error: set_host_name " ) &
                  em_value( hostExpr ) &
                  pl( " failed" )
             );
          end;
       end if;
       if hasPort then
          begin
            Set_Port( connection.C, integer( to_numeric( portExpr ) ) );
          exception when others =>
             err( pl( "Internal error: set_port " ) &
                  em_value( portExpr ) &
                  pl( " failed" )
             );
          end;
       end if;
       Connect( connection.C );
       currentEngine := Engine_MySQL;
     exception when not_connected =>
        if hasHost and hasPort and not boolean( maintenanceOpt ) then
           err( pl( "database connection failed - User " & User( connection.C ) & ", Password " & Password( connection.C )
           & ", Host " & Host_Name( connection.C ) & "and Port " & integer'image( Port( connection.C ) ) & " " & DB_Name( connection.C ) ) );
        else
          err( +"database connection failed" );
        end if;
     when already_connected =>
        err( +"already connected to database" );
     when STORAGE_ERROR =>
        err( +"out-of-memory or bad pointer" );
     when CONSTRAINT_ERROR =>
        err( +"constraint error exception" );
     when others =>
        err_exception_raised;
     end;
  end if;
end ParseMySQLMConnect;

#else

pragma warnings( off );
-- Hide unused parameters warnings

procedure not_configured is
begin
  err( +"mysql support not configured" );
end not_configured;

procedure not_configured( result : out unbounded_string; kind : out identifier ) is
begin
  err( +"mysql support not configured" );
end not_configured;

procedure ParseMySQLMConnect renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMEngineOf( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: t := mysqlm.engine_of( C );
  -- Source: APQ.Engine_Of
  connectId   : identifier;
  connection : resPtr;
begin
  kind := db_database_type_t;
  expect( mysqlm_engine_of_t );
  ParseSingleInOutParameter( mysqlm_engine_of_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_unbounded_string( integer'image( Database_Type'pos( Engine_Of( connection.C ) ) ) );
       if length( result ) > 0 then
          if element( result, 1 ) = ' ' then
             delete( result, 1, 1 );
          end if;
       end if;
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMEngineOf;

#else

procedure ParseMySQLMEngineOf( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
--#if MYSQL
--
--procedure ParseMySQLMPrepare( result : out unbounded_string ) is
---- TODO: this not implemented
--  -- Syntax: r := mysql.prepare( query, sqlstmt [,after] );
--  sqlExpr   : unbounded_string;
--  sqlType   : identifier;
--  afterExpr : unbounded_string;
--  afterType : identifier;
--  hasAfter  : boolean := false;
--  queryExpr : unbounded_string;
--  queryType : identifier;
--  query : resPtr;
--begin
--  expect( mysqlm_prepare_t );
--  ParseFirstNumericParameter( queryExpr, queryType, mysqlm_query_t );
--  ParseExpression( sqlExpr, sqlType );
--  if baseTypesOK( string_t, sqlType ) then
--     if token = symbol_t and identifiers( token ).value = "," then
--        expect( symbol_t, "," );
--        ParseExpression( afterExpr, afterType );
--        if baseTypesOK( string_t, sqlType ) then
--           hasAfter := true;
--        end if;
--     end if;
--  end if;
--  expect( symbol_t, ")" );
--  if isExecutingCommand then
--     result := to_bush_boolean( true );
--     begin
--       findQuery( queryExpr, query );
--       Clear( query.Q );
--       if hasAfter then
--          Prepare( query.Q, to_string( sqlExpr ), to_string( afterExpr ) );
--       else
--          Prepare( query.Q, to_string( sqlExpr ) );
--       end if;
--     exception when others =>
--       result := to_bush_boolean( false );
--     end;
--  end if;
--end ParseMySQLMPrepare;
--
--#else
--
--procedure ParseMySQLMPrepare( result : out unbounded_string ) renames not_configured;
--
--#end if;
#if MYSQL

procedure ParseMySQLMPrepare is
  -- Syntax: mysqlm.prepare( Q, sqlstmt [,after] );
  sqlExpr   : unbounded_string;
  sqlType   : identifier;
  afterExpr : unbounded_string;
  afterType : identifier;
  hasAfter  : boolean := false;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_prepare_t );
  ParseFirstInOutParameter( mysqlm_prepare_t, queryId, mysqlm_query_t );
  ParseNextStringParameter( mysqlm_prepare_t, sqlExpr, sqlType, string_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_prepare_t, afterExpr, afterType, string_t );
     hasAfter := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       if hasAfter then
          Prepare( query.Q, to_string( sqlExpr ), to_string( afterExpr ) );
       else
          Prepare( query.Q, to_string( sqlExpr ) );
       end if;
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMPrepare;

#else

procedure ParseMySQLMPrepare renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMAppend is
  -- Syntax: mysqlm.append( query, sqlstmt [,after] );
  sqlExpr   : unbounded_string;
  sqlType   : identifier;
  afterExpr : unbounded_string;
  afterType : identifier;
  hasAfter  : boolean := false;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_append_t );
  ParseFirstInOutParameter( mysqlm_append_t, queryId, mysqlm_query_t );
  ParseNextStringParameter( mysqlm_append_t, sqlExpr, sqlType, string_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_append_t, afterExpr, afterType, string_t );
     hasAfter := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       if hasAfter then
          Append( query.Q, to_string( sqlExpr ), to_string( afterExpr ) );
       else
          Append( query.Q, to_string( sqlExpr ) );
       end if;
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMAppend;

#else

procedure ParseMySQLMAppend renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMAppendLine is
  -- Syntax: mysqlm.append_line( query, sqlstmt );
  sqlExpr   : unbounded_string;
  sqlType   : identifier;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_append_line_t );
  ParseFirstInOutParameter( mysqlm_append_line_t, queryId, mysqlm_query_t );
  ParseLastStringParameter( mysqlm_append_line_t, sqlExpr, sqlType, string_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       Append_Line( query.Q, to_string( sqlExpr ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMAppendLine;

#else

procedure ParseMySQLMAppendLine renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMAppendQuoted is
  -- Syntax: mysqlm.append_quoted( query, connect, sqlstmt [,after] );
  sqlExpr   : unbounded_string;
  sqlType   : identifier;
  afterExpr : unbounded_string;
  afterType : identifier;
  hasAfter  : boolean := false;
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_append_quoted_t );
  ParseFirstInOutParameter( mysqlm_append_quoted_t, queryId, mysqlm_query_t );
  ParseNextInOutParameter( mysqlm_append_quoted_t, connectId, mysqlm_connection_t );
  ParseNextStringParameter( mysqlm_append_quoted_t, sqlExpr, sqlType, string_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_append_quoted_t, afterExpr, afterType, string_t );
     hasAfter := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       findQuery( queryId, query );
       if hasAfter then
          Append_Quoted( query.Q, connection.C, to_string( sqlExpr ), to_string( afterExpr ) );
       else
          Append_Quoted( query.Q, connection.C, to_string( sqlExpr ) );
       end if;
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMAppendQuoted;

#else

procedure ParseMySQLMAppendQuoted renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMExecute is
  -- execute( Q, C );
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_execute_t );
  ParseFirstInOutParameter( mysqlm_execute_t, queryId, mysqlm_query_t );
  ParseLastInOutParameter( mysqlm_execute_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       findQuery( queryId, query );
       Execute( query.Q, connection.C );
     exception when not_connected =>
       err( +"not connected" );
     when abort_state =>
       err( +"in abort state" );
     when sql_error =>
       err( pl( Error_Message( query.Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMExecute;

#else

procedure ParseMySQLMExecute renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMExecuteChecked is
  -- Syntax: mysqlm.execute_checked( Q, C [, msg ] );
  msgExpr   : unbounded_string;
  msgType   : identifier;
  hasMsg    : boolean := false;
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_execute_checked_t );
  ParseFirstInOutParameter( mysqlm_execute_checked_t, queryId, mysqlm_query_t );
  ParseNextInOutParameter( mysqlm_execute_checked_t, connectId, mysqlm_connection_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_execute_checked_t, msgExpr, msgType, string_t );
     hasMsg := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       findConnection( connectId, connection );
       if hasMsg then
          Execute_Checked( query.Q, connection.C, to_string( msgExpr ) );
       else
          Execute_Checked( query.Q, connection.C );
       end if;
     exception when not_connected =>
       err( +"not connected" );
     when abort_state =>
       err( +"in abort state" );
     when sql_error =>
       err( pl( Error_Message( query.Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMExecuteChecked;

#else

procedure ParseMySQLMExecuteChecked renames not_configured;

#end if;

--procedure ParseDBDo( result : out unbounded_string ) is
--begin
--  expect( db_do_t );
--  result := null_unbounded_string;
--end ParseDBDo;
--
--procedure ParseDBFetchrow( result : out unbounded_string ) is
--begin
--  expect( db_fetchrow_t );
--  result := null_unbounded_string;
--end ParseDBFetchrow;
#if MYSQL

procedure ParseMySQLMDisconnect is
  -- Syntax: mysqlm.disconnect;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_disconnect_t );
  ParseSingleInOutParameter( mysqlm_disconnect_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
        findConnection( connectId, connection );
        Disconnect( connection.C );
     exception when not_connected =>
        err( +"no database connection" );
     when already_connected =>
        err( +"already connected to database" );
     when others =>
        err_exception_raised;
     end;
  end if;
end ParseMySQLMDisconnect;

#else

procedure ParseMySQLMDisconnect renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMIsConnected( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: mysqlm.is_connected
  connectId   : identifier;
  connection : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_is_connected_t );
  ParseSingleInOutParameter( mysqlm_is_connected_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_bush_boolean( is_connected( connection.C ) );
     exception when others =>
       result := to_bush_boolean( false );
     end;
  end if;
end ParseMySQLMIsConnected;

#else

procedure ParseMySQLMIsConnected( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMReset is
  -- Syntax: mysqlm.reset( c )
  -- Source: APQ.Reset
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_reset_t );
  ParseSingleInOutParameter( mysqlm_reset_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       Reset( connection.C );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMReset;

#else

procedure ParseMySQLMReset renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMErrorMessage( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: mysqlm.error_message
  -- Source: APQ.Error_Message
  connectId : identifier;
  connection : resPtr;
begin
  kind := string_t;
  expect( mysqlm_error_message_t );
  ParseSingleInOutParameter( mysqlm_error_message_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_unbounded_string( Error_Message( connection.C ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMErrorMessage;

-- procedure ParseMySQLNoticeMessage( result : out unbounded_string ) is
--   -- Syntax: mysql.notice_message
--   -- Source: APQ.Notice_Message
-- begin
--   expect( mysqlm_notice_message_t );
--   if isExecutingCommand then
--      begin
--        result := to_unbounded_string( Notice_Message( C ) );
--      exception when others =>
--        err( "exception was raised" );
--      end;
--   end if;
-- end ParseMySQLNoticeMessage;

#else

procedure ParseMySQLMErrorMessage( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMInAbortState( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: mysqlm.in_abort_state( c )
  -- Source: APQ.In_Abort_State
  connectId : identifier;
  connection : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_in_abort_state_t );
  ParseSingleInOutParameter( mysqlm_in_abort_state_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_bush_boolean( In_Abort_State( connection.C ) );
     exception when not_connected =>
       err( +"not connected" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMInAbortState;

#else

procedure ParseMySQLMInAbortState( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMOptions( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: mysqlm.options( c )
  -- Source: APQ.Options
  connectId   : identifier;
  connection : resPtr;
begin
  kind := string_t;
  expect( mysqlm_options_t );
  ParseSingleInOutParameter( mysqlm_options_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_unbounded_string( Options( connection.C ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMOptions;

#else

procedure ParseMySQLMOptions( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMSetRollbackOnFinalize is
  -- Syntax: mysqlm.set_rollback_on_finalize( C, b );
  -- Source: APQ.Set_Rollback_On_Finalize
  rollExpr : unbounded_string;
  rollType : identifier;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_set_rollback_on_finalize_t );
  ParseFirstInOutParameter( mysqlm_set_rollback_on_finalize_t, connectId, mysqlm_connection_t );
  ParseLastEnumParameter( mysqlm_set_rollback_on_finalize_t, rollExpr, rollType, boolean_t );
  if isExecutingCommand then
     declare
       rollback : constant boolean := rollExpr = to_unbounded_string( "1" );
     begin
       findConnection( connectId, connection );
       Set_Rollback_On_Finalize( connection.C, rollback );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMSetRollbackOnFinalize;

#else

procedure ParseMySQLMSetRollbackOnFinalize renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMWillRollbackOnFinalize( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: b := mysqlm.will_rollback_on_finalize( C );
  -- Source: APQ.Will_Rollback_On_Finalize
  connectId   : identifier;
  connection : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_will_rollback_on_finalize_t );
  ParseSingleInOutParameter( mysqlm_will_rollback_on_finalize_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_bush_boolean( Will_Rollback_On_Finalize( connection.C ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMWillRollbackOnFinalize;

#else

procedure ParseMySQLMWillRollbackOnFinalize( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMOpenDBTrace is
  -- Syntax: mysqlm.open_db_trace( C, f [,m] );
  -- Source: APQ.Open_DB_Trace
  fnameExpr : unbounded_string;
  fnameType : identifier;
  modeExpr  : unbounded_string;
  modeType  : identifier;
  traceMode : trace_mode_type;
  hasMode  : boolean := false;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_open_db_trace_t );
  ParseFirstInOutParameter( mysqlm_open_db_trace_t, connectId, mysqlm_connection_t );
  ParseNextStringParameter( mysqlm_open_db_trace_t, fnameExpr, fnameType, string_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextStringParameter( mysqlm_open_db_trace_t, modeExpr, modeType, db_trace_mode_type_t );
     traceMode := Trace_Mode_Type'val( integer'value( ' ' & to_string( modeExpr ) ) );
     hasMode := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       if hasMode then
          Open_DB_Trace( connection.C, to_string( fnameExpr ), traceMode );
       else
          Open_DB_Trace( connection.C, to_string( fnameExpr ) );
       end if;
     exception when not_connected =>
       err( +"not connected" );
     when tracing_state =>
       err( +"file already open" );
     when Ada.IO_Exceptions.Name_Error =>
       err( +"file not found" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMOpenDBTrace;

#else

procedure ParseMySQLMOpenDBTrace renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMCloseDBTrace is
  -- Syntax: mysqlm.close_db_trace( C );
  -- Source: APQ.Close_DB_Trace
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_close_db_trace_t );
  ParseSingleInOutParameter( mysqlm_close_db_trace_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       Close_DB_Trace( connection.C );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMCloseDBTrace;

#else

procedure ParseMySQLMCloseDBTrace renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMSetTrace is
  -- Syntax: mysqlm.set_trace( C, b );
  -- Source: APQ.Set_Trace
  traceExpr : unbounded_string;
  traceType : identifier;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_set_trace_t );
  ParseFirstInOutParameter( mysqlm_set_trace_t, connectId, mysqlm_connection_t );
  ParseLastEnumParameter( mysqlm_set_trace_t, traceExpr, traceType, boolean_t );
  if isExecutingCommand then
     declare
       traceback : constant boolean := traceExpr = to_unbounded_string( "1" );
     begin
       findConnection( connectId, connection );
       Set_Trace( connection.C, traceback );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMSetTrace;

#else

procedure ParseMySQLMSetTrace renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMIsTrace( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: b := mysqlm.is_trace( C );
  -- Source: APQ.Is_Trace
  connectId   : identifier;
  connection : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_is_trace_t );
  ParseSingleInOutParameter( mysqlm_is_trace_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       result := to_bush_boolean( Is_Trace( connection.C ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMIsTrace;

#else

procedure ParseMySQLMIsTrace( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMClear is
  -- Syntax: mysql.clear( Q );
  -- Source: APQ.Clear
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_clear_t );
  ParseSingleInOutParameter( mysqlm_clear_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       Clear( query.Q );
     exception when CONSTRAINT_ERROR =>
       err( +"constraint error exception" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMClear;

#else

procedure ParseMySQLMClear renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMRaiseExceptions is
  -- Syntax: mysqlm.raise_exceptions( Q, [ b ] );
  -- Source: APQ.Raise_Exceptions
  raiseExpr : unbounded_string;
  raiseType : identifier;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_raise_exceptions_t );
  ParseFirstInOutParameter( mysqlm_raise_exceptions_t, queryId, mysqlm_query_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextEnumParameter( mysqlm_raise_exceptions_t, raiseExpr, raiseType, boolean_t );
  else
     raiseExpr := to_unbounded_string( "1" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     declare
       raise_them : constant boolean := raiseExpr = to_unbounded_string( "1" );
     begin
       findQuery( queryId, query );
       Raise_Exceptions( query.Q, raise_them );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMRaiseExceptions;

#else

procedure ParseMySQLMRaiseExceptions renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMReportErrors is
  -- Syntax: mysqlm.report_errors( Q, b );
  -- Source: APQ.Report_Errors
  reportExpr : unbounded_string;
  reportType : identifier;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_report_errors_t );
  ParseFirstInOutParameter( mysqlm_report_errors_t, queryId, mysqlm_query_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextEnumParameter( mysqlm_report_errors_t, reportExpr, reportType, boolean_t );
  else
     reportExpr := to_unbounded_string( "1" );
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     declare
       report_them : constant boolean := reportExpr = to_unbounded_string( "1" );
     begin
       findQuery( queryId, query );
       Report_Errors( query.Q, report_them );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMReportErrors;

#else

procedure ParseMySQLMReportErrors renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMBeginWork is
  -- Syntax: mysqlm.begin_work(Q, C );
  -- Source: APQ.Begin_Work
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_begin_work_t );
  ParseFirstInOutParameter( mysqlm_begin_work_t, queryId, mysqlm_query_t );
  ParseLastInOutParameter( mysqlm_begin_work_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       findConnection( connectId, connection );
       Begin_Work( query.Q, connection.C );
     exception when abort_state =>
       err( +"in abort state" );
     when not_connected =>
       err( +"not connected" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMBeginWork;

#else

procedure ParseMySQLMBeginWork renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMRollbackWork is
  -- Syntax: mysqlm.rollback_work( Q, C );
  -- Source: APQ.Rollback_Work
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_rollback_work_t );
  ParseFirstInOutParameter( mysqlm_rollback_work_t, queryId, mysqlm_query_t );
  ParseLastInOutParameter( mysqlm_rollback_work_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       findQuery( queryId, query );
       Rollback_Work( query.Q, connection.C );
     exception when abort_state =>
       err( +"in abort state" );
     when not_connected =>
       err( +"not connected" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMRollbackWork;

#else

procedure ParseMySQLMRollbackWork renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMCommitWork is
  -- Syntax: mysqlm.commit_work( Q, C );
  -- Source: APQ.Commit_Work
  queryId   : identifier;
  query : resPtr;
  connectId   : identifier;
  connection : resPtr;
begin
  expect( mysqlm_commit_work_t );
  ParseFirstInOutParameter( mysqlm_commit_work_t, queryId, mysqlm_query_t );
  ParseLastInOutParameter( mysqlm_commit_work_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       findQuery( queryId, query );
       Commit_Work( query.Q, connection.C );
     exception when abort_state =>
       err( +"in abort state" );
     when not_connected =>
       err( +"not connected" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMCommitWork;

#else

procedure ParseMySQLMCommitWork renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMRewind is
  -- Syntax: mysqlm.rewind( Q );
  -- Source: APQ.Rewind
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_rewind_t );
  ParseSingleInOutParameter( mysqlm_rewind_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       Rewind( query.Q );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMRewind;

#else

procedure ParseMySQLMRewind renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMFetch is
  -- Syntax: mysqlm.fetch( q [,i] )
  -- Source: APQ.Fetch
  expr_val : unbounded_string;
  expr_type : identifier;
  haveIndex : boolean := false;
  queryId   : identifier;
  query : resPtr;
begin
  expect( mysqlm_fetch_t );
  ParseFirstInOutParameter( mysqlm_fetch_t, queryId, mysqlm_query_t );
  if token = symbol_t and identifiers( token ).value.all = "," then
     ParseNextNumericParameter( mysqlm_fetch_t, expr_val, expr_type, mysqlm_tuple_index_type_t );
     haveIndex := true;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       if haveIndex then
          Fetch( query.Q, Tuple_Index_Type( to_numeric( expr_val ) ) );
       else
          Fetch( query.Q );
       end if;
     exception when no_tuple =>
       err( +"no tuple" );
     when no_result =>
       err( +"no result" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMFetch;

#else

procedure ParseMySQLMFetch renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMEndOfQuery( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: b := mysqlm.end_of_query( Q );
  -- Source: APQ.End_Of_Query
  queryId : identifier;
  query : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_end_of_query_t );
  ParseSingleInOutParameter( mysqlm_end_of_query_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_bush_boolean( End_Of_Query( query.Q ) );
     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMEndOfQuery;

#else

procedure ParseMySQLMEndOfQuery( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMTuple( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: t := mysql.tuple( Q );
  -- Source: APQ.Tuple
  queryId : identifier;
  query : resPtr;
begin
  kind := mysqlm_tuple_index_type_t;
  expect( mysqlm_tuple_t );
  ParseSingleInOutParameter( mysqlm_tuple_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_unbounded_string( Tuple_Index_Type'image( Tuple( query.Q ) ) );
     exception when no_tuple =>
       err( +"no tuple" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMTuple;

#else

procedure ParseMySQLMTuple( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMTuples( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.tuples( Q );
  -- Source: APQ.Tuples
  queryId : identifier;
  query : resPtr;
begin
  kind := mysqlm_tuple_count_type_t;
  expect( mysqlm_tuples_t );
  ParseSingleInOutParameter( mysqlm_tuples_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_unbounded_string( Tuple_Count_Type'image( Tuples( query.Q ) ) );
     exception when no_result =>
       err( +"no result" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMTuples;

#else

procedure ParseMySQLMTuples( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMColumns( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.columns( Q );
  -- Source: APQ.Columns
  queryId : identifier;
  query : resPtr;
begin
  kind := natural_t;
  expect( mysqlm_columns_t );
  ParseSingleInOutParameter( mysqlm_columns_t, queryId, mysqlm_query_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_unbounded_string( Natural'image( Columns( query.Q ) ) );
     exception when no_result =>
       err( +"no result" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMColumns;

#else

procedure ParseMySQLMColumns( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMColumnName( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.column_Name( Q, c );
  -- Source: APQ.Column_Name;
  exprVal : unbounded_string;
  exprType : identifier;
  queryId : identifier;
  query : resPtr;
begin
  kind := string_t;
  expect( mysqlm_column_name_t );
  ParseFirstInOutParameter( mysqlm_column_name_t, queryId, mysqlm_query_t );
  ParseLastNumericParameter( mysqlm_column_name_t, exprVal, exprType, mysqlm_column_index_type_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_unbounded_string(
          Column_Name( query.Q, Column_Index_Type( to_numeric( exprVal ) ) )
       );
     exception when no_column =>
       err( +"no column" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMColumnName;

#else

procedure ParseMySQLMColumnName( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMColumnIndex( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.column_index( Q, s );
  -- Source: APQ.Column_Index
  exprVal : unbounded_string;
  exprType : identifier;
  queryId : identifier;
  query : resPtr;
begin
  kind := mysqlm_column_index_type_t;
  expect( mysqlm_column_index_t );
  ParseFirstInOutParameter( mysqlm_column_index_t, queryId, mysqlm_query_t );
  ParseLastStringParameter( mysqlm_column_index_t, exprVal, exprType, string_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_unbounded_string( Column_Index_Type'image(
           Column_Index( query.Q, to_string( exprVal ) )
       ) );
     exception when no_column =>
       err( +"no column" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMColumnIndex;

#else

procedure ParseMySQLMColumnIndex( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

--procedure ParseDBColumnType( result : out unbounded_string ) is
  -- Syntax: n := db.column_type;
  -- Source: APQ.Column_Type;
  --exprVal : unbounded_string;
  --exprType : identifier;
--begin
  --expect( db_column_type_t );
  --expect( symbol_t, "(" );
  --ParseExpression( exprVal, exprType );
  --if baseTypesOK( exprType, db_column_index_type_t ) then
     --expect( symbol_t, ")" );
  --end if;
  --if isExecutingCommand then
     --begin
       --result := to_unbounded_string(
          --Column_Type( Q,
          --Column_Index_Type( to_numeric( exprVal ) ) )
       --);
     --exception when no_column =>
       --err( "no column" );
     --when no_result =>
       --err( "no result" );
     --when others =>
       --err( "exception was raised" );
     --end;
  --end if;
--end ParseDBColumnType;

procedure ParseMySQLMIsNull( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.is_null( Q, i );
  -- Source: APQ.Is_Null;
  exprVal : unbounded_string;
  exprType : identifier;
  queryId : identifier;
  query : resPtr;
begin
  kind := boolean_t;
  expect( mysqlm_is_null_t );
  ParseFirstInOutParameter( mysqlm_is_null_t, queryId, mysqlm_query_t );
  ParseLastNumericParameter( mysqlm_is_null_t, exprVal, exprType, mysqlm_column_index_type_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := to_bush_boolean(
          Is_Null( query.Q,
          Column_Index_Type( to_numeric( exprVal ) ) )
       );
     exception when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMIsNull;

#else

procedure ParseMySQLMIsNull( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMValue( result : out unbounded_string; kind : out identifier ) is
  -- Syntax: n := mysqlm.value( Q );
  -- Source: APQ.Value;
  exprVal  : unbounded_string;
  exprType : identifier;
  queryId  : identifier;
  query : resPtr;
begin
  kind := universal_t;
  expect( mysqlm_value_t );
  ParseFirstInOutParameter( mysqlm_value_t, queryId, mysqlm_query_t );
  ParseLastNumericParameter( mysqlm_value_t, exprVal, exprType, mysqlm_column_index_type_t );
  if isExecutingCommand then
     begin
       findQuery( queryId, query );
       result := Value( query.Q, Column_Index_Type( to_numeric( exprVal ) ) );
     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMValue;

#else

procedure ParseMySQLMValue( result : out unbounded_string; kind : out identifier ) renames not_configured;

#end if;

#if MYSQL

procedure MySQLMShowIt( Q : in out APQ.MySQL.Client.Query_Type; C : resPtr ) is
-- run SQL command and display results in tabular format
  tabularDisplay : boolean := false;
  headingWidth   : integer := 0;
  wasNull        : boolean := false;
  columnWidths   : array( 1..32 ) of integer;
  totalWidth     : integer;
  width          : integer;
begin
  if isExecutingCommand then
     begin
     if is_connected( C.C ) then
        execute( Q, C.C );

        -- Initially, the columns widths are the widths of the headings

        for i in 1..columns( Q ) loop
            columnWidths( i ) := column_name( Q, Column_Index_Type( i ) )'length;
            if columnWidths( i ) < 4 then -- room for NULL on tabular display
               columnWidths( i ) := 4;
            end if;
            if headingWidth < columnWidths( i ) then
               headingWidth := columnWidths( i );
            end if;
        end loop;

        -- Check query results and adjust the columns widths for the longest
        -- results.

        while not end_of_query( Q ) loop
              fetch( Q );
              for i in 1..columns( Q ) loop
                 if not is_null( Q, Column_Index_Type( i ) ) then
                    width := length( ada.strings.unbounded.to_unbounded_string( Value( Q, Column_Index_Type( i ) ) ) );
                    if width > 256 then
                       width := 256;
                    end if;
                    if width > columnWidths( i ) then
                       columnWidths( i ) := width;
                    end if;
                 end if;
              end loop;
        end loop;

        -- Add up all columns for the total width for a tabular display

        totalWidth := 2;                                        -- left/right marg
        for i in 1..columns( Q ) loop
            totalWidth := totalWidth + columnWidths( i );       -- width of column
            if i /= columns( Q ) then                             -- not last col?
               totalWidth := totalWidth + 3;                    -- 3 char sep
            end if;
        end loop;

        -- Rewind the clear and prepare to show the results

        Rewind( Q );
        new_line_retry;

        -- Use a tabular display only if will fit in the current display

        tabularDisplay := totalWidth <= integer( displayInfo.col );

        -- Draw the columns

        if tabularDisplay then
           put_retry( " " );
           for i in 1..columns( Q ) loop
               put_retry(
                  to_string(
                      ada.strings.unbounded.Head(
                          to_unbounded_string(
                              column_name( Q, Column_Index_Type( i ) ) )
                      , columnWidths( i ) )
                  )
               );
               if i /= columns( Q ) then
                  put_retry( " " & utf_verticalLine & " " );
               end if;
           end loop;
           new_line_retry;
           put_retry( utf_horizontalLine );
           for i in 1..columns( Q ) loop
               put_retry( ada.strings.unbounded.to_string( columnWidths( i ) * utf_horizontalLine ) );
               if i /= columns( Q ) then
                  put_retry( utf_horizontalLine & utf_crossedLines & utf_horizontalLine );
               else
                  put_retry( utf_horizontalLine );
               end if;
           end loop;
           new_line_retry;
        end if;

        -- Draw the query results

        while not end_of_query( Q ) loop
            fetch( Q );
            if tabularDisplay then
               put_retry( " " );
            end if;
            for i in 1..columns( Q ) loop
                if tabularDisplay then
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( to_string( ada.strings.unbounded.Head( to_unbounded_string( "NULL" ), columnWidths( i ) ) ) ) );
                      wasNull := true;
                   else
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                   end if;
                   if i /= columns( Q ) then
                      put_retry( " " & utf_verticalLine & " " );
                   end if;
                else
                   put_retry( to_string( ada.strings.unbounded.head( to_unbounded_string( column_name( Q, Column_Index_Type( i ) ) ), headingWidth ) ) );
                   put_retry( ": " );
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( "NULL" ) );
                      wasNull := true;
                   else
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) );
                   end if;
                   new_line_retry;
                end if;
                exit when wasSIGINT or wasSIGTERM;
            end loop;
            new_line_retry;
        end loop;
     end if;

     -- Draw the summary line

     if tuples( Q ) > 1 and columns ( Q ) > 1 then
        if tuples( Q ) = 1 then
           put_retry( " 1 Row" );
        else
           put_retry( Tuple_Index_Type'image( tuples( Q ) ) );
           put_retry( " Rows" );
        end if;
        if wasNull then
           put_retry( " with nulls" );
        end if;
        if columns( Q ) = 1 then
           put_retry( " and 1 Column" );
        else
           put_retry( " and" );
           put_retry( integer'image( columns( Q ) ) );
           put_retry( " Columns" );
        end if;
        new_line_retry;
     end if;
     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when sql_error =>
       err( pl( Error_Message( Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end MySQLMShowIt;

procedure ParseMySQLMShow is
  -- Syntax: mysqlm.show( Q, C );
  -- Source: N/A
  queryId : identifier;
  query : resPtr;
  connectionId : identifier;
  connection : resPtr;
begin
  expect( mysqlm_show_t );
  ParseFirstInOutParameter( mysqlm_show_t, queryId, mysqlm_query_t );
  ParseLastInOutParameter( mysqlm_show_t, connectionId, mysqlm_connection_t );
  if isExecutingCommand then
     findConnection( connectionId, connection );
     findQuery( queryId, query );
     MySQLMShowIt( Q => query.Q, C => connection );
  end if;
end ParseMySQLMShow;

#else

procedure ParseMySQLMShow renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMList is
  -- Syntax: mysqlm.list( C )
  -- Source: N/A
  tabularDisplay : boolean := false;
  headingWidth   : integer := 0;
  wasNull        : boolean := false;
  columnWidths   : array( 1..32 ) of integer;
  totalWidth     : integer;
  width          : integer;
  connectId   : identifier;
  connection : resPtr;
  Q : APQ.MySQL.Client.Query_Type;
begin
  expect( mysqlm_list_t );
  ParseSingleInOutParameter( mysqlm_list_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
       --dbname := to_unbounded_string( DB_Name( connection.C ) );
     if is_connected( connection.C ) then
        -- Show tablename and kind, lookup owner from another table.
        -- the database name is table_schema column
        -- Owner not implemented yet - DEBUG

        prepare( Q, "select table_name, table_type, engine from " &
                    "information_schema.tables where table_schema = '" &
                    DB_Name( connection.C ) & "' order by table_name desc" );

        -- This is the PostgreSQL query...
        -- prepare( Q, "select n.nspname as " & '"' & "Schema" & '"' &
        --   ", c.relname as " & '"' & "Name" & '"' &
        --   ", c.relkind as " & '"' & "Type" & '"' &
        --   ", u.usename as " &  '"' & "Owner" & '"' &
        --   " from pg_class c, pg_user u, pg_namespace n where u.usesysid = c.relowner and n.oid = c.relnamespace and c.relkind <> 't' and c.relkind <> 'i' and u.usesysid <> 1 order by c.relname" );

        -- MySQL 5.0.x has information_schema.  Older versions must fall back
        -- to a show query.

        begin
           execute( Q, connection.C );
        exception when others =>
           prepare( Q, "show tables" );
           execute( Q, connection.C );
        end;

        -- Initially, the columns widths are the widths of the headings

        for i in 1..columns( Q ) loop
            columnWidths( i ) := column_name( Q, Column_Index_Type( i ) )'length;
            if columnWidths( i ) < 4 then -- room for NULL on tabular display
               columnWidths( i ) := 4;
            end if;
            if headingWidth < columnWidths( i ) then
               headingWidth := columnWidths( i );
            end if;
        end loop;

        -- Check query results and adjust the columns widths for the longest
        -- results.

        while not end_of_query( Q ) loop
              fetch( Q );
              for i in 1..columns( Q ) loop
                 if not is_null( Q, Column_Index_Type( i ) ) then
                    if i = 2 then -- column 2 is table type
                       width := length( ada.strings.unbounded.to_unbounded_string( mysql_kind_to_string( Value( Q, Column_Index_Type( i ) ) ) ) );
                    elsif i = 3 then -- column 3 is engine type
                       width := length( ada.strings.unbounded.to_unbounded_string( mysql_engine_to_string( Value( Q, Column_Index_Type( i ) ) ) ) );
                    else
                       width := length( ada.strings.unbounded.to_unbounded_string( Value( Q, Column_Index_Type( i ) ) ) );
                    end if;
                    if width > 256 then
                       width := 256;
                    end if;
                    if width > columnWidths( i ) then
                       columnWidths( i ) := width;
                    end if;
                 end if;
              end loop;
              exit when wasSIGINT or wasSIGTERM;
        end loop;

        -- Add up all columns for the total width for a tabular display

        totalWidth := 2;                                        -- left/right marg
        for i in 1..columns( Q ) loop
            totalWidth := totalWidth + columnWidths( i );       -- width of column
            if i /= columns(Q) then                             -- not last col?
               totalWidth := totalWidth + 3;                    -- 3 char sep
            end if;
        end loop;

        -- Rewind the clear and prepare to show the results

        Rewind( Q );
        new_line_retry;

        -- Use a tabular display only if will fit in the current display

        tabularDisplay := totalWidth <= integer( displayInfo.col );

        -- Draw the columns

        if tabularDisplay then
           put_retry( " " );
           for i in 1..columns( Q ) loop
               put_retry(
                  to_string(
                      ada.strings.unbounded.Head(
                          to_unbounded_string(
                              column_name( Q, Column_Index_Type( i ) ) )
                      , columnWidths( i ) )
                  )
               );
               if i /= columns( Q ) then
                  put_retry( " " & utf_verticalLine & " " );
               end if;
           end loop;
           new_line_retry;
           put_retry( utf_horizontalLine );
           for i in 1..columns( Q ) loop
               put_retry( ada.strings.unbounded.to_string( columnWidths( i ) * utf_horizontalLine ) );
               if i /= columns( Q ) then
                  put_retry( utf_horizontalLine & utf_crossedLines & utf_horizontalLine );
               else
                  put_retry( utf_horizontalLine );
               end if;
           end loop;
           new_line_retry;
        end if;

        -- Draw the query results

        while not end_of_query( Q ) loop
            fetch( Q );
            if tabularDisplay then
               put_retry( " " );
            end if;
            for i in 1..columns( Q ) loop
                if tabularDisplay then
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( to_string( ada.strings.unbounded.Head( to_unbounded_string( "NULL" ), columnWidths( i ) ) ) ) );
                      wasNull := true;
                   elsif i = 2 then -- column 2 is table type
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_kind_to_string( value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                   elsif i = 3 then -- column 3 is engine type
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_engine_to_string( value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                   else
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                   end if;
                   if i /= columns( Q ) then
                      put_retry( " " & utf_verticalLine & " " );
                   end if;
                else
                   put_retry( to_string( ada.strings.unbounded.head( to_unbounded_string( column_name( Q, Column_Index_Type( i ) ) ), headingWidth ) ) );
                   put_retry( ": " );
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( "NULL" ) );
                      wasNull := true;
                   elsif i = 2 then
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_kind_to_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                   elsif i = 3 then
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_engine_to_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                   else
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) );
                   end if;
                   new_line_retry;
                end if;
            end loop;
            new_line_retry;
            exit when wasSIGINT or wasSIGTERM;
        end loop;
     end if;

     -- Draw the summary line

     if tuples( Q ) = 1 then
        put_retry( " 1 Row" );
     else
        put_retry( Tuple_Index_Type'image( tuples( Q ) ) );
        put_retry( " Rows" );
     end if;
     if wasNull then
        put_retry( " with nulls" );
     end if;
     if columns( Q ) = 1 then
        put_retry( " and 1 Column" );
     else
        put_retry( " and" );
        put_retry( integer'image( columns( Q ) ) );
        put_retry( " Columns" );
     end if;
     new_line_retry;
     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when sql_error =>
       err( pl( Error_Message( Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMList;

#else

procedure ParseMySQLMList renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMSchema is
  -- Syntax: mysqlm.schema( C, "table" );
  -- Source: N/A
  tabularDisplay : boolean := false;
  headingWidth   : integer := 0;
  wasNull        : boolean := false;
  columnWidths   : array( 1..32 ) of integer;
  totalWidth     : integer;
  width          : integer;
  exprType       : identifier;
  exprVal        : unbounded_string;
  connectId   : identifier;
  connection : resPtr;
  Q : APQ.MySQL.Client.Query_Type;
begin
  expect( mysqlm_schema_t );
  ParseFirstInOutParameter( mysqlm_schema_t, connectId, mysqlm_connection_t );
  ParseLastStringParameter( mysqlm_schema_t, exprVal, exprType, string_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
     if is_connected( connection.C ) then
        -- Find column name, type, type length, not null and default flags
        -- Don't show dropped columns, columns with system types (oid, etc).
        -- The PostgreSQL query.
        -- prepare( Q, "select a.attname as " & '"' & "Column" & '"' &
        --            ", t.typname as " & '"' & "Type" & '"' &
        --            ", a.atttypmod-4 as " & '"' & "Length" & '"' &
        --            ", a.attnotnull as " & '"' & "Not Null" & '"' &
        --            ", a.atthasdef as " & '"' & "Default" & '"' &
        --            "from pg_attribute a, pg_class c, pg_type t where a.attrelid = c.oid and t.oid = a.atttypid and a.attisdropped = 'f' and (a.atttypid < 26 or a.atttypid > 29) and c.relname='" &
        --             to_string( exprVal ) & "' order by a.attnum" );

        prepare( Q, "show column_name, data_type, is_nullable, column_default from information_schema.columns where table_name = '" & to_string( exprVal ) & "'" );

        -- MySQL 5.0.x has information_schema.  Older versions must fall back
        -- to a show query.

        begin
           execute( Q, connection.C );
        exception when others =>
           prepare( Q, "show columns from " & to_string( exprVal ) );
           execute( Q, connection.C );
        end;

        -- No result? Then table was not found.

        if end_of_query( Q ) then
           err( +"Did not find any relation named " &
              unb_em( exprVal ) );
           return;
        end if;

        -- Initially, the columns widths are the widths of the headings

        for i in 1..columns( Q ) loop
            if i /= 3 then -- column 3 is type length (not shown)
               columnWidths( i ) := column_name( Q, Column_Index_Type( i ) )'length;
               if columnWidths( i ) < 4 then -- room for NULL on tabular display
                  columnWidths( i ) := 4;
               end if;
               if headingWidth < columnWidths( i ) then
                  headingWidth := columnWidths( i );
               end if;
            end if;
        end loop;

        -- Check query results and adjust the columns widths for the longest
        -- results.

        while not end_of_query( Q ) loop
              fetch( Q );
              for i in 1..columns( Q ) loop
                 if i /= 3 then -- column 3 is type length (not shown)
                    if not is_null( Q, Column_Index_Type( i ) ) then
                       if i = 2 then -- column 2 is column type
                          width := length( ada.strings.unbounded.to_unbounded_string(
                                    mysql_column_type_to_string( Value( Q, Column_Index_Type( i ) ),
                                                              Value( Q, Column_Index_Type( i+1 ) ) ) ) );
                       elsif i = 4 then
                          width := length( ada.strings.unbounded.to_unbounded_string(
                                   mysql_not_null_to_string( Value( Q, Column_Index_Type( i ) ) ) ) );
                       elsif i = 5 then
                          width := length( ada.strings.unbounded.to_unbounded_string(
                                   mysql_default_to_string( Value( Q, Column_Index_Type( i ) ) ) ) );
                       else
                          width := length( ada.strings.unbounded.to_unbounded_string( Value( Q, Column_Index_Type( i ) ) ) );
                       end if;
                       if width > 256 then
                          width := 256;
                       end if;
                       if width > columnWidths( i ) then
                          columnWidths( i ) := width;
                       end if;
                    end if;
                 end if;
              end loop;
        end loop;

        -- Add up all columns for the total width for a tabular display

        totalWidth := 2;                                        -- left/right marg
        for i in 1..columns( Q ) loop
            if i /= 3 then -- column 3 is type length (not shown)
               totalWidth := totalWidth + columnWidths( i );       -- width of column
               if i /= columns(Q) then                             -- not last col?
                  totalWidth := totalWidth + 3;                    -- 3 char sep
               end if;
            end if;
        end loop;

        -- Rewind the clear and prepare to show the results

        Rewind( Q );
        new_line_retry;

        -- Use a tabular display only if will fit in the current display

        tabularDisplay := totalWidth <= integer( displayInfo.col );

        -- Draw the columns

        if tabularDisplay then
           put_retry( " " );
           for i in 1..columns( Q ) loop
               if i /= 3 then -- column 3 is type length (not shown)
                  put_retry(
                     to_string(
                         ada.strings.unbounded.Head(
                             to_unbounded_string(
                                 column_name( Q, Column_Index_Type( i ) ) )
                         , columnWidths( i ) )
                     )
                  );
                  if i /= columns( Q ) then
                     put_retry( " " & utf_verticalLine & " " );
                  end if;
               end if;
           end loop;
           new_line_retry;
           put_retry( utf_horizontalLine );
           for i in 1..columns( Q ) loop
               if i /= 3 then -- column 3 is type length (not shown)
                  put_retry( ada.strings.unbounded.to_string( columnWidths( i ) * utf_horizontalLine ) );
                  if i /= columns( Q ) then
                     put_retry( utf_horizontalLine & utf_crossedLines & utf_horizontalLine );
                  else
                     put_retry( utf_horizontalLine );
                  end if;
               end if;
           end loop;
           new_line_retry;
        end if;

        -- Draw the query results

        while not end_of_query( Q ) loop
            fetch( Q );
            if tabularDisplay then
               put_retry( " " );
            end if;
            for i in 1..columns( Q ) loop
                if i /= 3 then -- column 3 is type length (not shown)
                   if tabularDisplay then
                      if is_null( Q, Column_Index_Type( i ) ) then
                         put_retry( inverse( to_string( ada.strings.unbounded.Head( to_unbounded_string( "NULL" ), columnWidths( i ) ) ) ) );
                         wasNull := true;
                      elsif i = 2 then -- column 2 is column type
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_column_type_to_string( value( Q, Column_Index_Type( i ) ), Value( Q, Column_Index_Type( i+1 ) ) ) ) ), columnWidths( i ) ) ) );
                      elsif i = 4 then
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_not_null_to_string( value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                      elsif i = 5 then
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_default_to_string( value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                      else
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                      end if;
                      if i /= columns( Q ) then
                         put_retry( " " & utf_verticalLine & " " );
                      end if;
                   else
                      put_retry( to_string( ada.strings.unbounded.head( to_unbounded_string( column_name( Q, Column_Index_Type( i ) ) ), headingWidth ) ) );
                      put_retry( ": " );
                      if is_null( Q, Column_Index_Type( i ) ) then
                         put_retry( inverse( "NULL" ) );
                         wasNull := true;
                      elsif i = 2 then
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_column_type_to_string( value( Q, Column_Index_Type( i ) ), Value( Q, Column_Index_Type( i+1 ) ) ) ) ) ) ) );
                      elsif i = 4 then
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_not_null_to_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                      elsif i = 5 then
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_default_to_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                      else
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) );
                      end if;
                      new_line_retry;
                   end if;
                end if;
            end loop;
            new_line_retry;
        end loop;
     end if;

     -- Draw the summary line

     if tuples( Q ) = 1 then
        put_retry( " 1 Row" );
     else
        put_retry( Tuple_Index_Type'image( tuples( Q ) ) );
        put_retry( " Rows" );
     end if;
     if wasNull then
        put_retry( " with nulls" );
     end if;
     if columns( Q ) = 1 then
        put_retry( "and 1 Column" );
     else
        put_retry( " and" );
        put_retry( integer'image( columns( Q ) ) );
        put_retry( " Columns" );
     end if;
     new_line_retry;

     -- List of indexes: not available yet through information_schema

     prepare( Q, "show index from " & to_string( exprVal ) );
     execute( Q, connection.C );

     if tuples( Q ) > 0 then
        put_line_retry( "Indexes:" );
        while not end_of_query( Q ) loop
            fetch( Q );
            put_retry( "    " );
            declare
               indexName   : constant string := Value( Q, 1 );
               primaryKey  : constant string := Value( Q, 2 );
               uniqueIndex : constant string := Value( Q, 3 );
               colList     : constant string := Value( Q, 4 );
            begin
               put_retry( bold( indexName ) );
               put_retry( " " );
               if primaryKey = "t" then
                  put_retry( "primary key " );
               end if;
               if uniqueIndex = "t" then
                  put_retry( "unique " );
               end if;
               put_retry( "on columns " );
               put_retry( bold( colList ) );
            end;
            new_line_retry;
        end loop;
     end if;

     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when sql_error =>
       err( pl( Error_Message( Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMSchema;

#else

procedure ParseMySQLMSchema renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMUsers is
  -- Syntax: mysqlm.users( c )
  -- Source: N/A
  tabularDisplay : boolean := false;
  headingWidth   : integer := 0;
  wasNull        : boolean := false;
  columnWidths   : array( 1..32 ) of integer;
  totalWidth     : integer;
  width          : integer;
  connectId   : identifier;
  connection : resPtr;
  Q : APQ.MySQL.Client.Query_Type;
begin
  expect( mysqlm_users_t );
  ParseSingleInOutParameter( mysqlm_users_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
     if is_connected( connection.C ) then

        -- Privileges are unique to a particular database engine

        prepare( Q, "select User as " & '"' & "User Name" & '"' & ", Host" &
                    ", Select_priv, Insert_priv, Update_priv, Delete_priv," &
                    "Create_priv, Drop_priv, Reload_priv, Shutdown_priv," &
                    "Process_priv, File_priv, Grant_priv, References_priv," &
                    "Index_priv, Show_db_priv, Super_priv," &
                    "Create_tmp_table_priv, Lock_tables_priv, Execute_priv," &
                    "Repl_slave_priv, Repl_client_priv from mysql.user" &
                    " order by User;" );
        execute( Q, connection.C );

        -- Initially, the columns widths are the widths of the headings

        for i in 1..columns( Q ) loop
            columnWidths( i ) := column_name( Q, Column_Index_Type( i ) )'length;
            if columnWidths( i ) < 4 then -- room for NULL on tabular display
               columnWidths( i ) := 4;
            end if;
            if headingWidth < columnWidths( i ) then
               headingWidth := columnWidths( i );
            end if;
        end loop;

        -- Check query results and adjust the columns widths for the longest
        -- results.

        while not end_of_query( Q ) loop
              fetch( Q );
              for i in 1..columns( Q ) loop
                 if not is_null( Q, Column_Index_Type( i ) ) then
                    if i = 3 then -- column 3 and 4 are attributes
                       width := length( ada.strings.unbounded.to_unbounded_string( mysql_userattributes_to_string( Value( Q, Column_Index_Type( i ) ), Value( Q, Column_Index_Type( i ) ) ) ) );
                    elsif i = 4 then
                       null;
                    else
                       width := length( ada.strings.unbounded.to_unbounded_string( Value( Q, Column_Index_Type( i ) ) ) );
                    end if;
                    if width > 256 then
                       width := 256;
                    end if;
                    if width > columnWidths( i ) then
                       columnWidths( i ) := width;
                    end if;
                 end if;
              end loop;
        end loop;

        -- Add up all columns for the total width for a tabular display

        totalWidth := 2;                                        -- left/right marg
        for i in 1..columns( Q ) loop
            totalWidth := totalWidth + columnWidths( i );       -- width of column
            if i /= columns(Q) then                             -- not last col?
               totalWidth := totalWidth + 3;                    -- 3 char sep
            end if;
        end loop;

        -- Rewind the clear and prepare to show the results

        Rewind( Q );
        new_line_retry;

        -- Use a tabular display only if will fit in the current display

        tabularDisplay := totalWidth <= integer( displayInfo.col );

        -- Draw the columns

        if tabularDisplay then
           put_retry( " " );
           for i in 1..columns( Q ) loop
               if i /= 4 then
                  put_retry(
                     to_string(
                         ada.strings.unbounded.Head(
                             to_unbounded_string(
                                 column_name( Q, Column_Index_Type( i ) ) )
                         , columnWidths( i ) )
                     )
                  );
                  if i /= 3 then
                     put_retry( " " & utf_verticalLine & " " );
                  end if;
               end if;
           end loop;
           new_line_retry;
           put_retry( utf_horizontalLine );
           for i in 1..columns( Q ) loop
               if i /= 4 then
                  put_retry( ada.strings.unbounded.to_string( columnWidths( i ) * utf_horizontalLine ) );
                  if i /= 3 then
                     put_retry( utf_horizontalLine & utf_crossedLines & utf_horizontalLine );
                  else
                     put_retry( utf_horizontalLine );
                  end if;
               end if;
           end loop;
           new_line_retry;
        end if;

        -- Draw the query results

        while not end_of_query( Q ) loop
            fetch( Q );
            if tabularDisplay then
               put_retry( " " );
            end if;
            for i in 1..columns( Q ) loop
                if i /= 4 then
                   if tabularDisplay then
                      if is_null( Q, Column_Index_Type( i ) ) then
                         put_retry( inverse( to_string( ada.strings.unbounded.Head( to_unbounded_string( "NULL" ), columnWidths( i ) ) ) ) );
                         wasNull := true;
                      elsif i = 2 then -- right-aligned
                         put_retry( to_string( Tail( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                      elsif i = 3 then -- column 3 and 4 are attributes
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_userattributes_to_string( value( Q, Column_Index_Type( i ) ), Value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                      else
                         put_retry( to_string( Head( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                      end if;
                      if i /= 3 then
                         put_retry( " " & utf_verticalLine & " " );
                      end if;
                   else
                      put_retry( to_string( ada.strings.unbounded.head( to_unbounded_string( column_name( Q, Column_Index_Type( i ) ) ), headingWidth ) ) );
                      put_retry( ": " );
                      if is_null( Q, Column_Index_Type( i ) ) then
                         put_retry( inverse( "NULL" ) );
                         wasNull := true;
                      elsif i = 3 then -- column 3 and 4 are attributes
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_userattributes_to_string( value( Q, Column_Index_Type( i ) ), Value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                      else
                         put_retry( bold( to_string( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) );
                      end if;
                      new_line_retry;
                   end if;
                end if;
            end loop;
            new_line_retry;
        end loop;
     end if;

     -- Draw the summary line

     if tuples( Q ) = 1 then
        put_retry( " 1 Row" );
     else
        put_retry( Tuple_Index_Type'image( tuples( Q ) ) );
        put_retry( " Rows" );
     end if;
     if wasNull then
        put_retry( " with nulls" );
     end if;
     if columns( Q ) = 1 then
        put_retry( "and 1 Column" );
     else
        put_retry( " and" );
        put_retry( integer'image( columns( Q ) ) );
        put_retry( " Columns" );
     end if;
     new_line_retry;
     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when sql_error =>
       err( pl( Error_Message( Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMUsers;

#else

procedure ParseMySQLMUsers renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMDatabases is
  -- Syntax: mysqlm.databases( c )
  -- Source: N/A
  tabularDisplay : boolean := false;
  headingWidth   : integer := 0;
  wasNull        : boolean := false;
  columnWidths   : array( 1..32 ) of integer;
  totalWidth     : integer;
  width          : integer;
  connectId   : identifier;
  connection : resPtr;
  Q : APQ.MySQL.Client.Query_Type;
begin
  expect( mysqlm_databases_t );
  ParseSingleInOutParameter( mysqlm_databases_t, connectId, mysqlm_connection_t );
  if isExecutingCommand then
     begin
       findConnection( connectId, connection );
     if is_connected( connection.C ) then
        -- Show tablename and kind, lookup owner from another table.
        -- Don't show tables owned by postgres (user 1), TOAST tables or indexes
        -- PostgreSQL query
        -- prepare( Q, "select d.datname as " & '"' & "Name" & '"' &
        --             ", u.usename as " & '"' & "Owner" & '"' &
        --             " from pg_database d, pg_user u where u.usesysid = d.datdba order by d.datname" );

        prepare( Q, "select schema_name as 'Database' from information_schema.schemata" );

        -- MySQL 5.0.x has information_schema.  Older versions must fall back
        -- to a show query.

        begin
           execute( Q, connection.C );
        exception when others =>
           prepare( Q, "show databases" );
           execute( Q, connection.C );
        end;

        -- Initially, the columns widths are the widths of the headings

        for i in 1..columns( Q ) loop
            columnWidths( i ) := column_name( Q, Column_Index_Type( i ) )'length;
            if columnWidths( i ) < 4 then -- room for NULL on tabular display
               columnWidths( i ) := 4;
            end if;
            if headingWidth < columnWidths( i ) then
               headingWidth := columnWidths( i );
            end if;
        end loop;

        -- Check query results and adjust the columns widths for the longest
        -- results.

        while not end_of_query( Q ) loop
              fetch( Q );
              for i in 1..columns( Q ) loop
                 if not is_null( Q, Column_Index_Type( i ) ) then
                    if i = 3 then -- column 2 is table type
                       width := length( ada.strings.unbounded.to_unbounded_string( mysql_kind_to_string( Value( Q, Column_Index_Type( i ) ) ) ) );
                    else
                       width := length( ada.strings.unbounded.to_unbounded_string( Value( Q, Column_Index_Type( i ) ) ) );
                    end if;
                    if width > 256 then
                       width := 256;
                    end if;
                    if width > columnWidths( i ) then
                       columnWidths( i ) := width;
                    end if;
                 end if;
              end loop;
        end loop;

        -- Add up all columns for the total width for a tabular display

        totalWidth := 2;                                        -- left/right marg
        for i in 1..columns( Q ) loop
            totalWidth := totalWidth + columnWidths( i );       -- width of column
            if i /= columns(Q) then                             -- not last col?
               totalWidth := totalWidth + 3;                    -- 3 char sep
            end if;
        end loop;

        -- Rewind the clear and prepare to show the results

        Rewind( Q );
        new_line_retry;

        -- Use a tabular display only if will fit in the current display

        tabularDisplay := totalWidth <= integer( displayInfo.col );

        -- Draw the columns

        if tabularDisplay then
           put_retry( " " );
           for i in 1..columns( Q ) loop
               put_retry(
                  to_string(
                      ada.strings.unbounded.Head(
                          to_unbounded_string(
                              column_name( Q, Column_Index_Type( i ) ) )
                      , columnWidths( i ) )
                  )
               );
               if i /= columns( Q ) then
                  put_retry( " " & utf_verticalLine & " " );
               end if;
           end loop;
           new_line_retry;
           put_retry( utf_horizontalLine );
           for i in 1..columns( Q ) loop
               put_retry( ada.strings.unbounded.to_string( columnWidths( i ) * utf_horizontalLine ) );
               if i /= columns( Q ) then
                  put_retry( utf_horizontalLine & utf_crossedLines & utf_horizontalLine );
               else
                  put_retry( utf_horizontalLine );
               end if;
           end loop;
           new_line_retry;
        end if;

        -- Draw the query results

        while not end_of_query( Q ) loop
            fetch( Q );
            if tabularDisplay then
               put_retry( " " );
            end if;
            for i in 1..columns( Q ) loop
                if tabularDisplay then
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( to_string( ada.strings.unbounded.Head( to_unbounded_string( "NULL" ), columnWidths( i ) ) ) ) );
                      wasNull := true;
                   elsif i = 3 then -- column 2 is table type
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( mysql_kind_to_string( value( Q, Column_Index_Type( i ) ) ) ) ), columnWidths( i ) ) ) );
                   else
                      put_retry( to_string( Head( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ), columnWidths( i ) ) ) );
                   end if;
                   if i /= columns( Q ) then
                      put_retry( " " & utf_verticalLine & " " );
                   end if;
                else
                   put_retry( to_string( ada.strings.unbounded.head( to_unbounded_string( column_name( Q, Column_Index_Type( i ) ) ), headingWidth ) ) );
                   put_retry( ": " );
                   if is_null( Q, Column_Index_Type( i ) ) then
                      put_retry( inverse( "NULL" ) );
                      wasNull := true;
                   elsif i = 3 then
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( mysql_kind_to_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) ) );
                   else
                      put_retry( bold( to_string( ToEscaped( to_unbounded_string( value( Q, Column_Index_Type( i ) ) ) ) ) ) );
                   end if;
                   new_line_retry;
                end if;
            end loop;
            new_line_retry;
        end loop;
     end if;

     -- Draw the summary line

     if tuples( Q ) = 1 then
        put_retry( " 1 Row" );
     else
        put_retry( Tuple_Index_Type'image( tuples( Q ) ) );
        put_retry( " Rows" );
     end if;
     if wasNull then
        put_retry( " with nulls" );
     end if;
     if columns( Q ) = 1 then
        put_retry( " and 1 Column" );
     else
        put_retry( " and" );
        put_retry( integer'image( columns( Q ) ) );
        put_retry( " Columns" );
     end if;
     new_line_retry;
     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     when sql_error =>
       err( pl( Error_Message( Q ) ) );
     when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMDatabases;

#else

procedure ParseMySQLMDatabases renames not_configured;

#end if;
#if MYSQL

--procedure DoMySQLMSQLSelect( sqlcmd : unbounded_string ) is
--  Q : APQ.MySQL.Client.Query_Type;
--begin
--  if isExecutingCommand then
--     prepare( Q, to_string( sqlcmd ) );
--err( "TODO: NO CONNECTION" ); -- DEBUG
--     MySQLShowIt( Q => Q, C => null  );
--  end if;
--end DoMySQLSQLSelect;

--procedure DoMySQLSQLStatement( sqlcmd : unbounded_string ) is
--  Q : APQ.MySQL.Client.Query_Type;
--C_dummy : APQ.MySQL.Client.Connection_Type; -- DEBUG
--begin
--  if isExecutingCommand then
--     prepare( Q, to_string( sqlcmd ) );
--     begin
--err( "TODO: NO CONNECTION" ); -- DEBUG
--       Execute( Q, connection => C_dummy );
--     exception when not_connected =>
--       err( "not connected" );
--     when abort_state =>
--       err( "in abort state" );
--     when sql_error =>
--       err( Error_Message( Q ) );
--     when others =>
--       err_exception_raised;
--     end;
--  end if;
--end DoMySQLSQLStatement;

procedure ParseMySQLMNewConnection is
  -- Syntax: mysqlm.new_connection( c )
  -- Source: N/A
  resId : resHandleId;
  ref : reference;
begin
  expect( mysqlm_new_connection_t );
  ParseSingleOutParameter( mysqlm_new_connection_t, ref, mysqlm_connection_t );
  if baseTypesOK( ref.kind, mysqlm_connection_t ) then
      null;
  end if;
  if isExecutingCommand then
     identifiers( ref.id ).resource := true;
     declareResource( resId, mysql_connection, getIdentifierBlock( ref.id ) );
     AssignParameter( ref, to_unbounded_string( resId ) );
  end if;
end ParseMySQLMNewConnection;

#else

procedure ParseMySQLMNewConnection renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMNewQuery is
  -- Syntax: mysqlm.new_connection( q )
  -- Source: N/A
  resId : resHandleId;
  ref : reference;
begin
  expect( mysqlm_new_query_t );
  ParseSingleOutParameter( mysqlm_new_query_t, ref, mysqlm_query_t );
  if baseTypesOK( ref.kind, mysqlm_query_t ) then
      null;
  end if;
  if isExecutingCommand then
     identifiers( ref.id ).resource := true;
     declareResource( resId, mysql_query, getIdentifierBlock( ref.id ) );
     AssignParameter( ref, to_unbounded_string( resId ) );
  end if;
end ParseMySQLMNewQuery;

#else

procedure ParseMySQLMNewQuery renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMFetchValues is
-- mysqlm.fetch_values( Q, C, r );
-- Fetch a row, reading all values into record r.  The column names must match
-- the fields in the record (the record fields need not be in the same order
-- as the query results).
  queryId : identifier;
  query : resPtr;
  connectionId : identifier;
  connection : resPtr;
  id : identifier;
  --ref : reference;
begin
  expect( mysqlm_fetch_values_t );
  ParseFirstInOutParameter( mysqlm_fetch_values_t, queryId, mysqlm_query_t );
  ParseNextInOutParameter( mysqlm_fetch_values_t, connectionId, mysqlm_connection_t );
  -- TODO: ref type is meaningless here since you can't autodeclare
  ParseLastInOutRecordParameter( mysqlm_fetch_values_t, id  );
-- TODO: is kind in the reference?
  if identifiers( getBaseType( identifiers( id ).kind ) ).kind /= root_record_t then
     err( +"Record type expected" );
  end if;
  if isExecutingCommand then
     declare
       fieldsInRecord : long_integer;
       searchName : unbounded_string;
       found : boolean;
       field : unbounded_string;
       elementKind : identifier;
     begin
       -- get the resources
       findConnection( connectionId, connection );
       findQuery( queryId, query );

       -- fetch the next row
       Fetch( query.q );

       -- confirm the length
-- TODO: is kind in the reference?
       fieldsInRecord := long_integer'value( to_string( identifiers( identifiers( id ).kind ).value.all ) );
--put_line( "fields in record = " &fieldsInRecord'img ); -- DEBUG
--put_line( "fields in query = " & integer'image( columns( query.Q ) ) ); -- DEBUG
       if long_integer( columns( query.Q ) ) /= fieldsInRecord then
          err( pl( "record has" & fieldsInRecord'img & " field(s) but the query returns" & integer'image( columns( query.Q ) ) ) );
       else
          for fieldNumber in 1..fieldsInRecord loop

          -- we have a label and a value.  the record field is stored in the
          -- symbol table as rec.field.  Prepend the record name and search
          -- the symbol table for the record field.  When found, cast the
          -- value and assign it.  Otherwise, if the field is not found, it
          -- is an error.
             found := false;

             searchName := identifiers( id ).name & "." & column_name( query.Q, column_index_type( fieldNumber ) );
             for j in reverse 1..identifiers_top-1 loop
                 if identifiers( j ).name = searchName then
                    found := true;
                    if not error_found then
                       field := to_unbounded_string( value( query.Q, Column_Index_Type( fieldNumber ) ) );
                       elementKind := getBaseType( identifiers( j ).kind );
                       if elementKind = boolean_t then
                          -- a MySQL boolean isn't exactly the same as an Ada boolean
                          -- Its boolean is a tiny int, non-zero is true
                          declare
                            lf : numericValue;
                          begin
                            lf := to_numeric( field );
                            if lf = 0.0 then
                               identifiers( j ).value.all := identifiers( false_t ).value.all;
                            else
                               identifiers( j ).value.all := identifiers( true_t ).value.all;
                            end if;
                          exception when constraint_error =>
                            err( unb_pl( searchName & " is boolean but query value is not numeric" ) );
                          end;
                       --elsif getUniType( elementKind ) = root_enumerated_t then
                       -- TODO: non-boolean enumerated.  See scanner.adb
                       -- for how to search for an enumerated item.
                       -- MySQL has no enumerated types (we could try mapping
                       -- a number or string but is that wise?)
                       elsif getUniType( elementKind ) = uni_string_t then
-- TODO: shouldn't catToType be used here to guarantee character values are
-- only one letter long (probably doesn't matter for JSON, but matters here)
                          -- identifiers( j ).value := field;
                          identifiers( j ).value.all := castToType( field,
                            identifiers( j ).kind );
                       elsif getUniType( elementKind ) = uni_numeric_t then
                          declare
                            lf : numericValue;
                          begin
                            lf := to_numeric( field );
                            identifiers( j ).value.all := castToType( field,
                              identifiers( j ).kind );
                          exception when constraint_error =>
                            err( unb_pl( searchName & " is numeric but query value is not numeric" ) );
                          end;
                       elsif getUniType( elementKind ) = universal_t then
                            identifiers( j ).value.all := field;
                       else
                          err( +"unsupported data type" );
                       end if;
                    end if;
                 end if;
             end loop;
             if not found then
                err( unb_em( searchName ) & pl( " does not exist" ) );
             end if;
          end loop;
       end if;

     exception when no_tuple =>
       err( +"no tuple" );
     when null_value =>
       err( +"null value" );
     when no_column =>
       err( +"no column" );
     when no_result =>
       err( +"no result" );
     --when sql_error =>
     --  err( Error_Message( Q ) );
     when others =>
       err_exception_raised;
       --if sourceLen = long_integer'value( to_string( identifiers( identifiers( target_var_id ).kind ).value ) ) then
-- TODO: no_coumn, no_result, null_value, no_tuple, etc exception handling
     end;
null;
  end if;
end ParseMySQLMFetchValues;

#else

procedure ParseMySQLMFetchValues renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMAppendForInsert is
-- mysqlm.append_for_insert( Q, C, r );
-- append the fields of record r to the query in the format of an ANSI SQL insert
-- " ( a, b, ... ) VALUES ( 'a', 'b', ... )"
  queryId : identifier;
  query : resPtr;
  connectionId : identifier;
  connection : resPtr;
  id : identifier;
begin
  expect( mysqlm_append_for_insert_t );
  ParseFirstInOutParameter( mysqlm_append_for_insert_t, queryId, mysqlm_query_t );
  ParseNextInOutParameter( mysqlm_append_for_insert_t, connectionId, mysqlm_connection_t );
  -- TODO: ref type is meaningless here since you can't autodeclare
  ParseLastInOutRecordParameter( mysqlm_append_for_insert_t, id  );
-- TODO: is kind in the reference?
  if identifiers( getBaseType( identifiers( id ).kind ) ).kind /= root_record_t then
     err( +"Record type expected" );
  end if;
  if isExecutingCommand then
     declare
       fieldName : unbounded_string;
       fieldSuffix : unbounded_string;
       dotPos : natural;
       field_t : identifier;
       elementKind : identifier;
       elementUniKind : identifier;
       firstField : boolean := true;
     begin
       -- get the resources
       findConnection( connectionId, connection );
       findQuery( queryId, query );

       append( query.Q, " (" );

       -- The basic logic is copied from DoRecordToJson()
       for i in 1..integer'value( to_string( identifiers( identifiers( id ).kind ).value.all ) ) loop
           for j in 1..identifiers_top-1 loop
               if identifiers( j ).field_of = identifiers( id ).kind then
                  if integer'value( to_string( identifiers( j ).value.all )) = i then
                     fieldName := identifiers( j ).name;
                     dotPos := length( fieldName );
                     while dotPos > 1 loop
                        exit when element( fieldName, dotPos ) = '.';
                        dotPos := dotPos - 1;
                     end loop;
                     fieldSuffix := delete( fieldName, 1, dotPos );
                     fieldName := identifiers( id ).name & "." & fieldSuffix;
                     findIdent( fieldName, field_t );

                     if firstField then
                        firstField := false;
                     else
                        Append( query.Q, "," );
                     end if;
                     Append( query.Q, fieldSuffix );
                  end if;
               end if;
           end loop;
       end loop;

       append( query.Q, " ) VALUES ( " );
       firstField := true;

       for i in 1..integer'value( to_string( identifiers( identifiers( id ).kind ).value.all ) ) loop
           for j in 1..identifiers_top-1 loop
               if identifiers( j ).field_of = identifiers( id ).kind then
                  if integer'value( to_string( identifiers( j ).value.all )) = i then
                     if firstField then
                        firstField := false;
                     else
                        Append( query.Q, "," );
                     end if;
                     fieldName := identifiers( j ).name;
                     dotPos := length( fieldName );
                     while dotPos > 1 loop
                        exit when element( fieldName, dotPos ) = '.';
                        dotPos := dotPos - 1;
                     end loop;
                     fieldSuffix := delete( fieldName, 1, dotPos );
                     fieldName := identifiers( id ).name & "." & fieldSuffix;
                     findIdent( fieldName, field_t );

                     elementKind := getBaseType( identifiers( field_t ).kind );
                     elementUniKind := getUniType( identifiers( field_t ).kind );

                     if elementKind = boolean_t then
                        Append( query.Q, to_string( identifiers( field_t ).value.all ) );
                     elsif elementUniKind = uni_string_t then
-- TODO: shouldn't catToType be used here to guarantee character values are
-- only one letter long (probably doesn't matter for JSON, but matters here)
                          -- identifiers( j ).value.all := field;
                        Append_Quoted( query.Q, connection.C, to_string( identifiers( field_t ).value.all ) );
                     elsif elementUniKind = uni_numeric_t then
                        Append( query.Q, to_string( identifiers( field_t ).value.all ) );
                     elsif elementUniKind = universal_t then
                        Append_Quoted( query.Q, connection.C, to_string( identifiers( field_t ).value.all ) );
                     else
                         err( +"unsupported data type" );
                     end if;
                  end if;
               end if;
           end loop;
       end loop;
       append( query.Q, " )" );

     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMAppendForInsert;

#else

procedure ParseMySQLMAppendForInsert renames not_configured;

#end if;
#if MYSQL

procedure ParseMySQLMAppendForUpdate is
-- mysqlm.append_for_update( Q, C, r );
-- append the fields of record r to the query in the format of an ANSI SQL update
-- " SET a = 'a', b = 'b', ..."
  queryId : identifier;
  query : resPtr;
  connectionId : identifier;
  connection : resPtr;
  id : identifier;
begin
  expect( mysqlm_append_for_update_t );
  ParseFirstInOutParameter( mysqlm_append_for_update_t, queryId, mysqlm_query_t );
  ParseNextInOutParameter( mysqlm_append_for_update_t, connectionId, mysqlm_connection_t );
  -- TODO: ref type is meaningless here since you can't autodeclare
  ParseLastInOutRecordParameter( mysqlm_append_for_update_t, id  );
-- TODO: is kind in the reference?
  if identifiers( getBaseType( identifiers( id ).kind ) ).kind /= root_record_t then
     err( +"Record type expected" );
  end if;
  if isExecutingCommand then
     declare
       fieldName : unbounded_string;
       fieldSuffix : unbounded_string;
       dotPos : natural;
       field_t : identifier;
       elementKind : identifier;
       elementUniKind : identifier;
       firstField : boolean := true;
     begin
       -- get the resources
       findConnection( connectionId, connection );
       findQuery( queryId, query );

       Append( query.Q, " SET " );
       -- The basic logic is copied from DoRecordToJson()
     for i in 1..integer'value( to_string( identifiers( identifiers( id ).kind ).value.all ) ) loop
         for j in 1..identifiers_top-1 loop
             if identifiers( j ).field_of = identifiers( id ).kind then
                if integer'value( to_string( identifiers( j ).value.all )) = i then
                   fieldName := identifiers( j ).name;
                   dotPos := length( fieldName );
                   while dotPos > 1 loop
                      exit when element( fieldName, dotPos ) = '.';
                      dotPos := dotPos - 1;
                   end loop;
                   fieldSuffix := delete( fieldName, 1, dotPos );
                   fieldName := identifiers( id ).name & "." & fieldSuffix;
                   findIdent( fieldName, field_t );

                   elementKind := getBaseType( identifiers( field_t ).kind );
                   elementUniKind := getUniType( identifiers( field_t ).kind );

                   if firstField then
                      firstField := false;
                   else
                      Append( query.Q, "," );
                   end if;
                   Append( query.Q, fieldSuffix & "=" );

                   if elementKind = boolean_t then
                      Append( query.Q, identifiers( field_t ).value.all );
                   elsif elementUniKind = uni_string_t then
-- TODO: shouldn't catToType be used here to guarantee character values are
-- only one letter long (probably doesn't matter for JSON, but matters here)
                          -- identifiers( j ).value.all := field;
                      Append_Quoted( query.Q, connection.C, identifiers( field_t ).value.all );
                   elsif getUniType( elementKind ) = uni_numeric_t then
                      Append( query.Q, identifiers( field_t ).value.all );
                   elsif getUniType( elementKind ) = universal_t then
                      Append_Quoted( query.Q, connection.C, identifiers( field_t ).value.all );
                   else
                       err( +"unsupported data type" );
                   end if;
                 end if;
             end if;
         end loop;
       end loop;
       Append( query.Q, " " );

     exception when others =>
       err_exception_raised;
     end;
  end if;
end ParseMySQLMAppendForUpdate;

#else

procedure ParseMySQLMAppendForUpdate renames not_configured;

#end if;

procedure StartupMySQLM is
begin
  declareNamespace( "mysqlm" );
  declareIdent( mysqlm_connection_t, "mysqlm.connection",
    positive_t, typeClass );
  identifiers( mysqlm_connection_t ).usage := limitedUsage;
  identifiers( mysqlm_connection_t ).resource := true;

  declareIdent( mysqlm_query_t, "mysqlm.query",
    positive_t, typeClass );

  declareIdent( mysqlm_column_index_type_t, "mysqlm.column_index_type",
    positive_t, typeClass );

  declareIdent( mysqlm_tuple_index_type_t, "mysqlm.tuple_index_type",
    positive_t, typeClass );

  declareIdent( mysqlm_tuple_count_type_t, "mysqlm.tuple_count_type",
    mysqlm_tuple_index_type_t, subClass );

  declareIdent( mysqlm_mode_type_t, "mysqlm.mode_type",
    root_enumerated_t, typeClass );
  declareStandardConstant( mysqlm_read_t, "mysqlm.read",
    mysqlm_mode_type_t, "0" );
  declareStandardConstant( mysqlm_write_t, "mysqlm.write",
    mysqlm_mode_type_t, "1" );
  declareStandardConstant( mysqlm_read_write_t, "mysqlm.read_write",
    mysqlm_mode_type_t, "2" );

--  declareStandardConstant( mysqlm_sequential_fetch_t, "mysql.sequential_fetch",
--    mysqlm_fetch_mode_type_t, "0" );
--  declareStandardConstant( mysqlm_random_fetch_t, "mysql.random_fetch",
--    mysqlm_fetch_mode_type_t, "1" );

  declareProcedure( mysqlm_new_connection_t, "mysqlm.new_connection", ParseMySQLMNewConnection'access );
  declareProcedure( mysqlm_new_query_t, "mysqlm.new_query", ParseMySQLMNewQuery'access );
  declareProcedure( mysqlm_connect_t, "mysqlm.connect", ParseMySQLMConnect'access );
  declareProcedure( mysqlm_disconnect_t, "mysqlm.disconnect", ParseMySQLMDisconnect'access );
  declareFunction( mysqlm_is_connected_t, "mysqlm.is_connected", ParseMySQLMIsConnected'access );
  declareProcedure( mysqlm_reset_t, "mysqlm.reset", ParseMySQLMReset'access );
  declareFunction( mysqlm_error_message_t, "mysqlm.error_message", ParseMySQLMErrorMessage'access );
  --declareFunction( mysqlm_notice_message_t, "mysqlm.notice_message" );
  declareFunction( mysqlm_in_abort_state_t, "mysqlm.in_abort_state", ParseMySQLMInAbortState'access );
  declareFunction( mysqlm_options_t, "mysqlm.options", ParseMySQLMOptions'access );
  declareFunction( mysqlm_will_rollback_on_finalize_t, "mysqlm.will_rollback_on_finalize", ParseMySQLMWillRollbackOnFinalize'access );
  declareProcedure( mysqlm_set_rollback_on_finalize_t, "mysqlm.set_rollback_on_finalize", ParseMySQLMSetRollbackOnFinalize'access );
  declareProcedure( mysqlm_open_db_trace_t, "mysqlm.open_db_trace", ParseMySQLMOpenDBTrace'access );
  declareProcedure( mysqlm_close_db_trace_t, "mysqlm.close_db_trace", ParseMySQLMCloseDBTrace'access );
  declareProcedure( mysqlm_set_trace_t, "mysqlm.set_trace", ParseMySQLMSetTrace'access );
  declareFunction( mysqlm_is_trace_t, "mysqlm.is_trace", ParseMySQLMIsTrace'access );
  declareProcedure( mysqlm_clear_t, "mysqlm.clear", ParseMySQLMClear'access );
  declareProcedure( mysqlm_prepare_t, "mysqlm.prepare", ParseMySQLMPrepare'access );
  declareProcedure( mysqlm_append_t, "mysqlm.append", ParseMySQLMAppend'access );
  declareProcedure( mysqlm_append_line_t, "mysqlm.append_line", ParseMySQLMAppendLine'access );
  declareProcedure( mysqlm_append_quoted_t, "mysqlm.append_quoted", ParseMySQLMAppendQuoted'access );
  declareProcedure( mysqlm_execute_t, "mysqlm.execute", ParseMySQLMExecute'access );
  declareProcedure( mysqlm_execute_checked_t, "mysqlm.execute_checked", ParseMySQLMExecuteChecked'access );
  declareProcedure( mysqlm_raise_exceptions_t, "mysqlm.raise_exceptions", ParseMySQLMRaiseExceptions'access );
  declareProcedure( mysqlm_report_errors_t, "mysqlm.report_errors", ParseMySQLMReportErrors'access );
  declareProcedure( mysqlm_begin_work_t, "mysqlm.begin_work", ParseMySQLMBeginWork'access );
  declareProcedure( mysqlm_rollback_work_t, "mysqlm.rollback_work", ParseMySQLMRollbackWork'access );
  declareProcedure( mysqlm_commit_work_t, "mysqlm.commit_work", ParseMySQLMCommitWork'access );
  declareProcedure( mysqlm_rewind_t, "mysqlm.rewind", ParseMySQLMRewind'access );
  declareProcedure( mysqlm_fetch_t, "mysqlm.fetch", ParseMySQLMFetch'access );
  declareFunction( mysqlm_end_of_query_t, "mysqlm.end_of_query", ParseMySQLMEndOfQuery'access );
  declareFunction( mysqlm_tuple_t, "mysqlm.tuple", ParseMySQLMTuple'access );
  declareFunction( mysqlm_tuples_t, "mysqlm.tuples", ParseMySQLMTuples'access );
  declareFunction( mysqlm_columns_t, "mysqlm.columns", ParseMySQLMColumns'access );
  declareFunction( mysqlm_column_name_t, "mysqlm.column_name", ParseMySQLMColumnName'access );
  declareFunction( mysqlm_column_index_t, "mysqlm.column_index", ParseMySQLMColumnIndex'access );
  --declareFunction( mysqlm_column_type_t, "mysqlm.column_type",  );
  declareFunction( mysqlm_is_null_t, "mysqlm.is_null", ParseMySQLMIsNull'access );
  declareFunction( mysqlm_value_t, "mysqlm.value", ParseMySQLMValue'access );
  declareFunction( mysqlm_engine_of_t, "mysqlm.engine_of", ParseMySQLMEngineOf'access );
  declareProcedure( mysqlm_show_t, "mysqlm.show", ParseMySQLMShow'access );
  declareProcedure( mysqlm_list_t, "mysqlm.list", ParseMySQLMList'access );
  declareProcedure( mysqlm_schema_t, "mysqlm.schema", ParseMySQLMSchema'access );
  declareProcedure( mysqlm_users_t, "mysqlm.users", ParseMySQLMUsers'access );
  declareProcedure( mysqlm_databases_t, "mysqlm.databases", ParseMySQLMDatabases'access );

  declareProcedure( mysqlm_fetch_values_t, "mysqlm.fetch_values", ParseMySQLMFetchValues'access );
  declareProcedure( mysqlm_append_for_insert_t, "mysqlm.append_for_insert", ParseMySQLMAppendForInsert'access );
  declareProcedure( mysqlm_append_for_update_t, "mysqlm.append_for_update", ParseMySQLMAppendForUpdate'access );

  --declareFunction( db_do_t, "db.do" );
  --declareFunction( db_fetchrow_t, "dbi.fetchrow" );

  --declareFunction( dbi_prepare_t, "dbi.prepare" );

  declareNamespaceClosed( "mysqlm" );
end StartupMySQLM;

procedure ShutdownMySQLM is
begin
#if MYSQL
  APQ.MySQL.Client.Shutdown;
#else
  null;
#end if;
end ShutdownMySQLM;

end parser_mysqlm;
