File csql_support.c¶
File List > cubrid > src > executables > csql_support.c
Go to the documentation of this file
/*
* Copyright 2008 Search Solution Corporation
* Copyright 2016 CUBRID Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* csql_support.c : Utilities for csql module
*/
#ident "$Id$"
#include "config.h"
#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#include <setjmp.h>
#include <assert.h>
#if defined(WINDOWS)
#include <io.h>
#else /* !WINDOWS */
#include <pwd.h>
#endif /* !WINDOWS */
#include "porting.h"
#include "csql.h"
#include "filesys.hpp"
#include "filesys_temp.hpp"
#include "memory_alloc.h"
#include "system_parameter.h"
#include "ddl_log.h"
#if defined (SUPPRESS_STRLEN_WARNING)
#define strlen(s1) ((int) strlen(s1))
#endif /* defined (SUPPRESS_STRLEN_WARNING) */
/* fixed stop position of a tab */
#define TAB_STOP 8
/* number of lines at each expansion of more line pointer array */
#define MORE_LINE_EXPANSION_UNIT 40
/* to build the current help message lines */
static char **iq_More_lines; /* more message lines */
static int iq_Num_more_lines = 0; /* number of more lines */
static jmp_buf iq_Jmp_buf;
#define DEFAULT_DB_ERROR_MSG_LEVEL 3 /* current max */
typedef enum csql_statement_state
{
CSQL_STATE_GENERAL = 0,
CSQL_STATE_C_COMMENT,
CSQL_STATE_CPP_COMMENT,
CSQL_STATE_SQL_COMMENT,
CSQL_STATE_SINGLE_QUOTE,
CSQL_STATE_MYSQL_QUOTE,
CSQL_STATE_DOUBLE_QUOTE_IDENTIFIER,
CSQL_STATE_BACKTICK_IDENTIFIER,
CSQL_STATE_BRACKET_IDENTIFIER,
CSQL_STATE_STATEMENT_END
} CSQL_STATEMENT_STATE;
typedef enum csql_statement_substate
{
CSQL_SUBSTATE_INITIAL = 0,
CSQL_SUBSTATE_SEEN_CREATE,
CSQL_SUBSTATE_SEEN_OR,
CSQL_SUBSTATE_SEEN_REPLACE,
CSQL_SUBSTATE_EXPECTING_IS_OR_AS,
CSQL_SUBSTATE_PL_LANG_SPEC,
CSQL_SUBSTATE_SEEN_LANGUAGE,
CSQL_SUBSTATE_PLCSQL_TEXT,
CSQL_SUBSTATE_SEEN_END
} CSQL_STATEMENT_SUBSTATE;
/* editor buffer management */
typedef struct
{
char *contents;
int data_size;
int alloc_size;
CSQL_STATEMENT_STATE state;
// following three fields are used to identify the beginning and the end of PL/CSQL texts
CSQL_STATEMENT_SUBSTATE substate;
int plcsql_begin_end_balance;
int plcsql_nest_level;
} CSQL_EDIT_CONTENTS;
static CSQL_EDIT_CONTENTS csql_Edit_contents = { NULL, 0, 0, CSQL_STATE_GENERAL, CSQL_SUBSTATE_INITIAL, 0, 0 };
static bool is_identifier_letter (const char c);
static bool match_word_ci (const char *word, const char **bufp);
static void iq_pipe_handler (int sig_no);
static void iq_format_err (char *string, int buf_size, int line_no, int col_no);
static bool iq_input_device_is_a_tty (void);
static bool iq_output_device_is_a_tty (void);
#if !defined(WINDOWS)
static int csql_get_user_home (char *homebuf, int bufsize);
#endif /* !WINDOWS */
/*
* iq_output_device_is_a_tty() - return if output stream is associated with
* a "tty" device.
* return: true if the output device is a terminal
*/
static bool
iq_output_device_is_a_tty ()
{
return (csql_Output_fp == stdout && isatty (fileno (stdout)));
}
/*
* iq_input_device_is_a_tty() - return if input stream is associated with
* a "tty" device.
* return: true if the input device is a terminal
*/
static bool
iq_input_device_is_a_tty ()
{
return (csql_Input_fp == stdin && isatty (fileno (stdin)));
}
#if !defined(WINDOWS)
/*
* csql_get_user_home() - get user home directory from /etc/passwd file
* return: 0 if success, -1 otherwise
* homedir(in/out) : user home directory
* homedir_size(in) : size of homedir buffer
*/
static int
csql_get_user_home (char *homedir, int homedir_size)
{
struct passwd *ptr = NULL;
uid_t userid = getuid ();
setpwent ();
while ((ptr = getpwent ()) != NULL)
{
if (userid == ptr->pw_uid)
{
snprintf (homedir, homedir_size, "%s", ptr->pw_dir);
endpwent ();
return NO_ERROR;
}
}
endpwent ();
return ER_FAILED;
}
#endif /* !WINDOWS */
/*
* csql_get_real_path() - get the real pathname (without wild/meta chars) using
* the default shell
* return: the real path name
* pathname(in)
*
* Note:
* the real path name returned from this function is valid until next this
* function call. The return string will not have any leading/trailing
* characters other than the path name itself. If error occurred from O.S,
* give up the extension and just return the `pathname'.
*/
char *
csql_get_real_path (const char *pathname)
{
#if defined(WINDOWS)
if (pathname == NULL)
{
return NULL;
}
while (isspace (pathname[0]))
{
pathname++;
}
if (pathname[0] == '\0')
{
return NULL;
}
return (char *) pathname;
#else /* ! WINDOWS */
static char real_path[PATH_MAX]; /* real path name */
char home[PATH_MAX];
if (pathname == NULL)
{
return NULL;
}
while (isspace (pathname[0]))
{
pathname++;
}
if (pathname[0] == '\0')
{
return NULL;
}
/*
* Do tilde-expansion here.
*/
if (pathname[0] == '~')
{
if (csql_get_user_home (home, sizeof (home)) != NO_ERROR)
{
return NULL;
}
snprintf (real_path, sizeof (real_path), "%s%s", home, &pathname[1]);
}
else
{
snprintf (real_path, sizeof (real_path), "%s", pathname);
}
return real_path;
#endif /* !WINDOWS */
}
/*
* csql_invoke_system() - execute the given command with the argument using
* system()
* return: none
* command(in)
*/
void
csql_invoke_system (const char *command)
{
bool error_found = false; /* TRUE if error found */
if (system (command) == 127)
{
error_found = true;
csql_Error_code = CSQL_ERR_OS_ERROR;
}
if (error_found)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
}
}
/*
* csql_invoke_formatter()
* return: CSQL_SUCCESS/CSQL_FAILURE
*
* Note:
* copy command editor buffer into temporary file and
* invoke formatter. After the format is finished,
* read the file into editor buffer.
*/
int
csql_invoke_formatter ()
{
/*create an unique file in tmp folder and open it for writing */
auto[before_filename, before_fileptr] = filesys::open_temp_file ("bef_fmt_");
if (before_fileptr == NULL)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
return CSQL_FAILURE;
}
filesys::auto_delete_file before_file_del (before_filename.c_str ());
filesys::auto_close_file before_file (before_fileptr);
if (csql_edit_write_file (before_file.get ()) == CSQL_FAILURE)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
fclose (before_file.release ());
/*create an unique file in tmp folder */
auto[after_filename, after_fileptr] = filesys::open_temp_file ("aft_fmt_");
if (after_fileptr == NULL)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
return CSQL_FAILURE;
}
filesys::auto_delete_file after_file_del (after_filename.c_str ());
filesys::auto_close_file after_file (after_fileptr);
/* invoke the formatter command */
char *cmd = csql_get_tmp_buf (strlen (csql_Formatter_cmd) + 1 + before_filename.size () + 3 + after_filename.size ());
if (cmd == NULL)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
fclose (after_file.release ());
sprintf (cmd, "%s %s > %s", csql_Formatter_cmd, before_filename.c_str (), after_filename.c_str ());
if (system (cmd) != 0)
{
free_and_init (cmd);
csql_Error_code = CSQL_ERR_FORMAT;
return CSQL_FAILURE;
}
/* initialize editor buffer */
csql_edit_contents_clear ();
free_and_init (cmd);
/*remove the file that saved before formatting command buffer */
before_file.reset (fopen (before_filename.c_str (), "r"));
if (!before_file)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
return CSQL_FAILURE;
}
/*remove the file that saved after formatting command buffer */
after_file.reset (fopen (after_filename.c_str (), "r"));
if (!after_file)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
return CSQL_FAILURE;
}
/* read the formatted file into editor */
if (csql_edit_read_file (after_file.get ()) == CSQL_FAILURE)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
return CSQL_SUCCESS;
}
/*
* csql_invoke_system_editor()
* return: CSQL_SUCCESS/CSQL_FAILURE
* argument: eidt session command argument input
*
* Note:
* copy command editor buffer into temporary file and
* invoke the user preferred system editor. After the
* edit is finished, read the file into editor buffer
*/
int
csql_invoke_system_editor (const char *argument)
{
if (!iq_output_device_is_a_tty ())
{
csql_Error_code = CSQL_ERR_CANT_EDIT;
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
if (csql_Formatter_cmd[0] != '\0' && argument && (!strcasecmp (argument, "format") || !strcasecmp (argument, "fmt")))
{
if (csql_invoke_formatter () != CSQL_SUCCESS)
{
return CSQL_FAILURE;
}
}
/* create an unique file in tmp folder and open it for writing */
auto[filename, fileptr] = filesys::open_temp_file ("csql_");
if (fileptr == NULL)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
filesys::auto_delete_file file_del (filename.c_str ()); //deletes file at scope end
filesys::auto_close_file file (fileptr); //closes file at scope end (before the above file deleter); forget about fp from now on
/* write the content of editor to the temp file */
if (csql_edit_write_file (file.get ()) == CSQL_FAILURE)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
/* invoke the system editor */
char *cmd = csql_get_tmp_buf (strlen (csql_Editor_cmd) + 1 + filename.size ());
if (cmd == NULL)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
fclose (file.release ()); //on Windows needs to be closed before being able to save from Notepad
sprintf (cmd, "%s %s", csql_Editor_cmd, filename.c_str ());
csql_invoke_system (cmd);
/* initialize editor buffer */
csql_edit_contents_clear ();
free_and_init (cmd);
file.reset (fopen (filename.c_str (), "r"));
if (!file)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
/* read the temp file into editor */
if (csql_edit_read_file (file.get ()) == CSQL_FAILURE)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
return CSQL_FAILURE;
}
return CSQL_SUCCESS;
}
/*
* csql_fputs()
* return: none
* str(in): string to be displayed
* fp(in) : FILE stream
*
* Note:
* `fputs' version to cope with "\1" in the string. This function displays
* `<', `>' alternatively.
*/
void
csql_fputs (const char *str, FILE * fp)
{
bool flag; /* toggled at every "\1" */
if (!fp)
{
return;
}
for (flag = false; *str != '\0'; str++)
{
if (*str == '\1')
{
putc ((flag) ? '>' : '<', fp);
flag = !flag;
}
else
{
putc (*str, fp);
}
}
}
/*
* csql_fputs_console_conv() - format and display a string to the CSQL console
* with console conversion applied, if available
* return: none
* str(in): string to be displayed
* fp(in) : FILE stream
*
* Note:
* `fputs' version to cope with "\1" in the string. This function displays
* `<', `>' alternatively.
*/
void
csql_fputs_console_conv (const char *str, FILE * fp)
{
char *conv_buf = NULL;
const char *conv_buf_ptr = NULL;
int conv_buf_size = 0;
if (!fp)
{
return;
}
if (csql_text_utf8_to_console != NULL
&& (*csql_text_utf8_to_console) (str, strlen (str), &conv_buf, &conv_buf_size) == NO_ERROR && conv_buf != NULL)
{
conv_buf_ptr = conv_buf;
}
else
{
conv_buf_ptr = str;
}
csql_fputs (conv_buf_ptr, fp);
if (conv_buf != NULL)
{
free (conv_buf);
}
}
/*
* csql_popen() - Open & return a pipe file stream to a pager
* return: pipe file stream to a pager if stdout is a tty,
* otherwise return fd.
* cmd(in) : popen command
* fd(in): currently open file descriptor
*
* Note: Caller should call csql_pclose() after done.
*/
FILE *
csql_popen (const char *cmd, FILE * fd)
{
#if defined(WINDOWS)
/* Nothing yet currently equivalent to the pagers on NT. Return iq_Output_fp so it can be simply sump stuff to the
* console. */
return fd;
#else /* ! WINDOWS */
FILE *pf; /* pipe stream to pager */
pf = fd;
if (cmd == NULL || cmd[0] == '\0')
{
return pf;
}
if (iq_output_device_is_a_tty () && iq_input_device_is_a_tty ())
{
pf = popen (cmd, "w");
if (pf == NULL)
{ /* pager failed, */
csql_Error_code = CSQL_ERR_CANT_EXEC_PAGER;
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
pf = fd;
}
}
else
{
pf = fd;
}
return (pf);
#endif /* ! WINDOWS */
}
/*
* csql_pclose(): close pipe file stream
* return: none
* pf(in): pipe stream pointer
* fd(in): This is the file descriptor for the output stream
* which was open prior to calling csql_popen().
*
* Note:
* We determine if it's a pipe by comparing the pipe stream pointer (pf)
* with the prior file descriptor (fd). If they are different, then a pipe
* was opened and will be closed.
*/
void
csql_pclose (FILE * pf, FILE * fd)
{
#if !defined(WINDOWS)
if (pf != fd)
{
pclose (pf);
}
#endif /* ! WINDOWS */
}
/*
* iq_format_err() - format an error string with line and/or column number
* return: none
* string(out): output string buffer
* line_no(in): error line number
* col_no(in) : error column number
*/
static void
iq_format_err (char *string, int buf_size, int line_no, int col_no)
{
if (line_no > 0)
{
if (col_no > 0)
snprintf (string, buf_size,
msgcat_message (MSGCAT_CATALOG_CSQL, MSGCAT_CSQL_SET_CSQL, CSQL_EXACT_POSITION_ERR_FORMAT), line_no,
col_no);
else
snprintf (string, buf_size,
msgcat_message (MSGCAT_CATALOG_CSQL, MSGCAT_CSQL_SET_CSQL, CSQL_START_POSITION_ERR_FORMAT), line_no);
strcat (string, "\n");
}
}
/*
* csql_display_csql_err() - display error message
* return: none
* line_no(in): error line number
* col_no(in) : error column number
*
* Note:
* if `line_no' is positive, this error is regarded as associated with
* the given line number. if `col_no' is positive, it represents the
* error position represents the exact position, otherwise it tells where
* the stmt starts.
*/
void
csql_display_csql_err (int line_no, int col_no)
{
csql_Error_code = CSQL_ERR_SQL_ERROR;
iq_format_err (csql_Scratch_text, SCRATCH_TEXT_LEN, line_no, col_no);
if (line_no > 0)
{
csql_fputs ("\n", csql_Error_fp);
csql_fputs_console_conv (csql_Scratch_text, csql_Error_fp);
}
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
}
/*
* csql_display_session_err() - display all query compilation errors
* for this session
* return: none
* session(in): context of query compilation
* line_no(in): statement starting line number
*/
void
csql_display_session_err (DB_SESSION * session, int line_no)
{
DB_SESSION_ERROR *err;
int col_no = 0;
csql_Error_code = CSQL_ERR_SQL_ERROR;
err = db_get_errors (session);
do
{
err = db_get_next_error (err, &line_no, &col_no);
if (line_no > 0)
{
csql_fputs ("\n", csql_Error_fp);
iq_format_err (csql_Scratch_text, SCRATCH_TEXT_LEN, line_no, col_no);
csql_fputs_console_conv (csql_Scratch_text, csql_Error_fp);
}
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
}
while (err);
return;
}
/*
* csql_append_more_line() - append the given line into the
* more message line array
* return: CSQL_FAILURE/CSQL_SUCCESS
* indent(in): number of blanks to be prefixed
* line(in): new line to be put
*
* Note:
* After usage of the more lines, caller should free by calling
* free_more_lines(). The line cannot have control characters except tab,
* new-line and "\1".
*/
int
csql_append_more_line (int indent, const char *line)
{
int i, j;
int n; /* register copy of num_more_lines */
int exp_len; /* length of lines after tab expand */
int new_num; /* new # of entries */
char *p;
const char *q;
char **t_lines; /* temp pointer */
char *conv_buf = NULL;
int conv_buf_size = 0;
if (csql_text_utf8_to_console != NULL
&& (*csql_text_utf8_to_console) (line, strlen (line), &conv_buf, &conv_buf_size) == NO_ERROR)
{
line = (conv_buf != NULL) ? conv_buf : line;
}
else
{
assert (conv_buf == NULL);
}
n = iq_Num_more_lines;
if (n % MORE_LINE_EXPANSION_UNIT == 0)
{
new_num = n + MORE_LINE_EXPANSION_UNIT;
if (n == 0)
{
t_lines = (char **) malloc (sizeof (char *) * new_num);
}
else
{
t_lines = (char **) realloc (iq_More_lines, sizeof (char *) * new_num);
}
if (t_lines == NULL)
{
csql_Error_code = CSQL_ERR_NO_MORE_MEMORY;
if (conv_buf != NULL)
{
assert (csql_text_utf8_to_console != NULL);
free_and_init (conv_buf);
}
return (CSQL_FAILURE);
}
iq_More_lines = t_lines;
}
/* calculate # of bytes should be allocated to store the given line in tab-expanded form */
for (i = exp_len = 0, q = line; *q != '\0'; q++)
{
if (*q == '\n')
{
exp_len += i + 1;
i = 0;
}
else if (*q == '\t')
{
i += TAB_STOP - i % TAB_STOP;
}
else
{
i++;
}
}
exp_len += i + 1;
iq_More_lines[n] = (char *) malloc (indent + exp_len);
if (iq_More_lines[n] == NULL)
{
csql_Error_code = CSQL_ERR_NO_MORE_MEMORY;
if (conv_buf != NULL)
{
assert (csql_text_utf8_to_console != NULL);
free_and_init (conv_buf);
}
return (CSQL_FAILURE);
}
for (i = 0, p = iq_More_lines[n]; i < indent; i++)
{
*p++ = ' ';
}
/* copy the line with tab expansion */
for (i = 0, q = line; *q != '\0'; q++)
{
if (*q == '\n')
{
*p++ = *q;
i = 0;
}
else if (*q == '\t')
{
for (j = TAB_STOP - i % TAB_STOP; j > 0; j--, i++)
{
*p++ = ' ';
}
}
else
{
*p++ = *q;
i++;
}
}
*p = '\0';
iq_Num_more_lines++;
if (conv_buf != NULL)
{
assert (csql_text_utf8_to_console != NULL);
free_and_init (conv_buf);
}
return (CSQL_SUCCESS);
}
/*
* csql_display_more_lines() - display lines in stdout.
* return: none
* title(in): optional title message
*
* Note: "\1" in line will be displayed `<' and `>', alternatively.
*/
void
csql_display_more_lines (const char *title)
{
int i;
FILE *pf; /* pipe stream to pager */
#if !defined(WINDOWS)
void (*iq_pipe_save) (int sig);
iq_pipe_save = signal (SIGPIPE, &iq_pipe_handler);
#endif /* ! WINDOWS */
if (setjmp (iq_Jmp_buf) == 0)
{
pf = csql_popen (csql_Pager_cmd, csql_Output_fp);
/* display title */
if (title != NULL)
{
sprintf (csql_Scratch_text, "\n=== %s ===\n\n", title);
csql_fputs (csql_Scratch_text, pf);
}
for (i = 0; i < iq_Num_more_lines; i++)
{
csql_fputs (iq_More_lines[i], pf);
putc ('\n', pf);
}
putc ('\n', pf);
csql_pclose (pf, csql_Output_fp);
}
#if !defined(WINDOWS)
signal (SIGPIPE, iq_pipe_save);
#endif /* ! WINDOWS */
}
/*
* csql_free_more_lines() - free more lines built by csql_append_more_line()
* return: none
*/
void
csql_free_more_lines (void)
{
int i;
if (iq_Num_more_lines > 0)
{
for (i = 0; i < iq_Num_more_lines; i++)
{
if (iq_More_lines[i] != NULL)
{
free_and_init (iq_More_lines[i]);
}
}
free_and_init (iq_More_lines);
iq_Num_more_lines = 0;
}
}
/*
* iq_pipe_handler() - Generic longjmp'ing signal handler used
* here we need to catch broken pipe
* return: none
* sig_no(in)
*
* Note:
*/
static void
iq_pipe_handler (int sig_no)
{
longjmp (iq_Jmp_buf, 1);
}
/*
* csql_check_server_down() - check if server is down
* return: none
*
* Note: If server is down, this function exit
*/
void
csql_check_server_down (void)
{
if (db_error_code () == ER_TM_SERVER_DOWN_UNILATERALLY_ABORTED)
{
nonscr_display_error (csql_Scratch_text, SCRATCH_TEXT_LEN);
fprintf (csql_Error_fp, "Exiting ...\n");
csql_exit (EXIT_FAILURE);
}
}
/*
* csql_get_tmp_buf()
* return: a pointer to a buffer for temporary formatting
* size(in): the number of characters required
*/
char *
csql_get_tmp_buf (size_t size)
{
static char *bufp = NULL;
static size_t bufsize = 0;
bufsize = size + 1;
bufp = (char *) malloc (bufsize);
if (bufp == NULL)
{
csql_Error_code = CSQL_ERR_NO_MORE_MEMORY;
bufsize = 0;
return NULL;
}
else
{
return bufp;
}
}
/*
* nonscr_display_error() - format error message with global error code
* return: none
* buffer(out): message ouput buffer
* buf_length(in): size of output buffer
*/
void
nonscr_display_error (char *buffer, int buf_length)
{
int remaining = buf_length;
char *msg;
const char *errmsg;
int len_errmsg;
char *con_buf_ptr = NULL;
int con_buf_size = 0;
strncpy (buffer, "\n", remaining);
remaining -= strlen ("\n");
msg = msgcat_message (MSGCAT_CATALOG_CSQL, MSGCAT_CSQL_SET_CSQL, CSQL_ERROR_PREFIX);
strncat (buffer, msg, remaining);
remaining -= strlen (msg);
errmsg = csql_errmsg (csql_Error_code);
len_errmsg = strlen (errmsg);
if (csql_text_utf8_to_console != NULL
&& (*csql_text_utf8_to_console) (errmsg, len_errmsg, &con_buf_ptr, &con_buf_size) == NO_ERROR)
{
if (con_buf_ptr != NULL)
{
errmsg = con_buf_ptr;
len_errmsg = con_buf_size;
}
}
if (len_errmsg > (remaining - 3) /* "\n\n" + NULL */ )
{
/* error msg will split into 2 pieces which is separated by "......" */
int print_len;
const char *separator = "......";
int separator_len = strlen (separator);
print_len = (remaining - 3 - separator_len) / 2;
strncat (buffer, errmsg, print_len); /* first half */
strcat (buffer, separator);
strncat (buffer, errmsg + len_errmsg - print_len, print_len); /* second half */
remaining -= (print_len * 2 + separator_len);
}
else
{
strncat (buffer, errmsg, remaining);
remaining -= len_errmsg;
}
if (con_buf_ptr != NULL)
{
free_and_init (con_buf_ptr);
}
strncat (buffer, "\n\n", remaining);
remaining -= strlen ("\n\n");
buffer[buf_length - 1] = '\0';
csql_fputs (buffer, csql_Error_fp);
logddl_set_err_msg (buffer);
}
/*
* csql_edit_buffer_get_data() - get string of current editor contents
* return: pointer of contents
*/
char *
csql_edit_contents_get ()
{
if (csql_Edit_contents.data_size <= 0)
{
return ((char *) "");
}
return csql_Edit_contents.contents;
}
static int
csql_edit_contents_expand (int required_size)
{
int new_alloc_size = csql_Edit_contents.alloc_size;
if (new_alloc_size >= required_size)
return CSQL_SUCCESS;
if (new_alloc_size <= 0)
{
new_alloc_size = 1024;
}
while (new_alloc_size < required_size)
{
new_alloc_size *= 2;
}
csql_Edit_contents.contents = (char *) realloc (csql_Edit_contents.contents, new_alloc_size);
if (csql_Edit_contents.contents == NULL)
{
csql_Edit_contents.alloc_size = 0;
csql_Error_code = CSQL_ERR_NO_MORE_MEMORY;
return CSQL_FAILURE;
}
csql_Edit_contents.alloc_size = new_alloc_size;
return CSQL_SUCCESS;
}
/*
* csql_edit_buffer_append() - append string to current editor contents
* return: CSQL_SUCCESS/CSQL_FAILURE
* str(in): string to append
* flag_append_new_line(in): whether or not to append new line char
*/
int
csql_edit_contents_append (const char *str, bool flag_append_new_line)
{
int str_len, new_data_size;
if (str == NULL)
{
return CSQL_SUCCESS;
}
str_len = strlen (str);
new_data_size = csql_Edit_contents.data_size + str_len;
if (csql_edit_contents_expand (new_data_size + 2) != CSQL_SUCCESS)
{
return CSQL_FAILURE;
}
memcpy (csql_Edit_contents.contents + csql_Edit_contents.data_size, str, str_len);
csql_Edit_contents.data_size = new_data_size;
if (flag_append_new_line)
{
csql_Edit_contents.contents[csql_Edit_contents.data_size++] = '\n';
}
csql_Edit_contents.contents[csql_Edit_contents.data_size] = '\0';
return CSQL_SUCCESS;
}
/*
* csql_walk_statement () - parse str and change the state
* return : NULL
* str (in) : the new statement chunk received from input
*/
void
csql_walk_statement (const char *str)
{
/* using flags but not adding many states in here may be not good choice, but it will not change the state machine
* model and save a lot of states. */
bool include_stmt = false;
bool is_last_stmt_valid = true;
const char *p;
int str_length;
if (str == NULL)
{
return;
}
CSQL_STATEMENT_STATE state = csql_Edit_contents.state;
CSQL_STATEMENT_SUBSTATE substate = csql_Edit_contents.substate;
int plcsql_begin_end_balance = csql_Edit_contents.plcsql_begin_end_balance;
int plcsql_nest_level = csql_Edit_contents.plcsql_nest_level;
assert ((plcsql_begin_end_balance == 0 && plcsql_nest_level == 0) ||
(substate == CSQL_SUBSTATE_PLCSQL_TEXT || substate == CSQL_SUBSTATE_SEEN_END));
if (state == CSQL_STATE_CPP_COMMENT || state == CSQL_STATE_SQL_COMMENT)
{
/* these are single line comments and we're parsing a new line */
state = CSQL_STATE_GENERAL;
}
if (state == CSQL_STATE_STATEMENT_END)
{
/* reset state in prev statement */
state = CSQL_STATE_GENERAL;
substate = CSQL_SUBSTATE_INITIAL;
}
str_length = strlen (str);
/* run as state machine */
for (p = str; p < str + str_length; p++)
{
switch (state)
{
case CSQL_STATE_GENERAL:
// eat up blanks
switch (*p)
{
case ' ':
case '\t':
case '\r':
case '\n':
continue;
}
// here, *p is a non-white-space
substate_transition:
switch (substate)
{
case CSQL_SUBSTATE_INITIAL:
if (match_word_ci ("create", &p))
{
substate = CSQL_SUBSTATE_SEEN_CREATE;
continue;
}
else
{
// keep the substate CSQL_SUBSTATE_INITIAL
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_SEEN_CREATE:
if (match_word_ci ("or", &p))
{
substate = CSQL_SUBSTATE_SEEN_OR;
continue;
}
else if (match_word_ci ("procedure", &p) || match_word_ci ("function", &p))
{
substate = CSQL_SUBSTATE_EXPECTING_IS_OR_AS;
continue;
}
else
{
substate = CSQL_SUBSTATE_INITIAL;
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_SEEN_OR:
if (match_word_ci ("replace", &p))
{
substate = CSQL_SUBSTATE_SEEN_REPLACE;
continue;
}
else
{
substate = CSQL_SUBSTATE_INITIAL;
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_SEEN_REPLACE:
if (match_word_ci ("procedure", &p) || match_word_ci ("function", &p))
{
substate = CSQL_SUBSTATE_EXPECTING_IS_OR_AS;
continue;
}
else
{
substate = CSQL_SUBSTATE_INITIAL;
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_EXPECTING_IS_OR_AS:
if (match_word_ci ("is", &p) || match_word_ci ("as", &p))
{
substate = CSQL_SUBSTATE_PL_LANG_SPEC;
continue;
}
else
{
// keep the substate CSQL_SUBSTATE_EXPECTING_IS_OR_AS
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_PL_LANG_SPEC:
if (match_word_ci ("language", &p))
{
substate = CSQL_SUBSTATE_SEEN_LANGUAGE;
continue;
}
else
{
// TRANSITION to CSQL_SUBSTATE_PLCSQL_TEXT!!!
substate = CSQL_SUBSTATE_PLCSQL_TEXT;
plcsql_begin_end_balance = 0;
plcsql_nest_level = 0;
goto substate_transition; // use goto to repeat a substate transition without increasing p
}
break;
case CSQL_SUBSTATE_SEEN_LANGUAGE:
if (match_word_ci ("java", &p))
{
substate = CSQL_SUBSTATE_INITIAL;
continue;
}
else if (match_word_ci ("plcsql", &p))
{
// TRANSITION to CSQL_SUBSTATE_PLCSQL_TEXT!!!
substate = CSQL_SUBSTATE_PLCSQL_TEXT;
plcsql_begin_end_balance = 0;
plcsql_nest_level = 0;
continue;
}
else
{
// syntax error
substate = CSQL_SUBSTATE_INITIAL;
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_PLCSQL_TEXT:
if (match_word_ci ("procedure", &p) || match_word_ci ("function", &p))
{
if (plcsql_begin_end_balance == 0)
{
plcsql_nest_level++;
}
continue;
}
else if (match_word_ci ("case", &p))
{
// case can start an expression and can appear in a balance 0 area
if (plcsql_begin_end_balance == 0)
{
plcsql_nest_level++;
}
plcsql_begin_end_balance++;
continue;
}
else if (match_word_ci ("begin", &p) || match_word_ci ("if", &p) || match_word_ci ("loop", &p))
{
plcsql_begin_end_balance++;
continue;
}
else if (match_word_ci ("end", &p))
{
substate = CSQL_SUBSTATE_SEEN_END;
continue;
}
else
{
// keep the substate CSQL_SUBSTATE_PLCSQL_TEXT
// break and proceed to the second switch
}
break;
case CSQL_SUBSTATE_SEEN_END:
plcsql_begin_end_balance--;
if (plcsql_begin_end_balance < 0)
{
// syntax error
plcsql_begin_end_balance = 0;
}
if (plcsql_begin_end_balance == 0)
{
plcsql_nest_level--;
if (plcsql_nest_level < 0)
{
// the last END closing PL/CSQL text was found
substate = CSQL_SUBSTATE_INITIAL;
plcsql_begin_end_balance = 0;
plcsql_nest_level = 0;
goto substate_transition; // use goto to repeat a substate transition without increasing p
}
}
substate = CSQL_SUBSTATE_PLCSQL_TEXT;
// match if/case/loop if exists, but just advance p and ignore them
if (match_word_ci ("if", &p) || match_word_ci ("case", &p) || match_word_ci ("loop", &p))
{
continue;
}
else
{
goto substate_transition; // use goto to repeat a substate transition without increasing p
}
break;
default:
assert (false); // unreachable
}
if (is_identifier_letter (*p))
{
if (!is_last_stmt_valid)
{
is_last_stmt_valid = true;
}
// once an identifier letter is found, advance p while the next letter is also an identifir letter
// in other words, consume the whole identifier
while (p + 1 < str + str_length && is_identifier_letter (*(p + 1)))
{
p++;
}
continue;
}
switch (*p)
{
case '/':
if (*(p + 1) == '/')
{
state = CSQL_STATE_CPP_COMMENT;
p++;
break;
}
if (*(p + 1) == '*')
{
state = CSQL_STATE_C_COMMENT;
p++;
break;
}
is_last_stmt_valid = true;
break;
case '-':
if (*(p + 1) == '-')
{
state = CSQL_STATE_SQL_COMMENT;
p++;
break;
}
is_last_stmt_valid = true;
break;
case '\'':
state = CSQL_STATE_SINGLE_QUOTE;
is_last_stmt_valid = true;
break;
case '"':
if (prm_get_bool_value (PRM_ID_ANSI_QUOTES) == false)
{
state = CSQL_STATE_MYSQL_QUOTE;
}
else
{
state = CSQL_STATE_DOUBLE_QUOTE_IDENTIFIER;
}
is_last_stmt_valid = true;
break;
case '`':
state = CSQL_STATE_BACKTICK_IDENTIFIER;
is_last_stmt_valid = true;
break;
case '[':
state = CSQL_STATE_BRACKET_IDENTIFIER;
is_last_stmt_valid = true;
break;
case ';':
if (substate != CSQL_SUBSTATE_PLCSQL_TEXT)
{
assert (substate != CSQL_SUBSTATE_SEEN_END);
include_stmt = true;
is_last_stmt_valid = false;
// initialize the state variables used to identify PL/CSQL text
substate = CSQL_SUBSTATE_INITIAL;
plcsql_begin_end_balance = 0;
plcsql_nest_level = 0;
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
assert (false); // unreachable
break;
default:
if (!is_last_stmt_valid)
{
is_last_stmt_valid = true;
}
break;
}
break;
case CSQL_STATE_C_COMMENT:
if (*p == '*' && *(p + 1) == '/')
{
state = CSQL_STATE_GENERAL;
p++;
break;
}
break;
case CSQL_STATE_CPP_COMMENT:
if (*p == '\n')
{
state = CSQL_STATE_GENERAL;
}
break;
case CSQL_STATE_SQL_COMMENT:
if (*p == '\n')
{
state = CSQL_STATE_GENERAL;
}
break;
case CSQL_STATE_SINGLE_QUOTE:
if (prm_get_bool_value (PRM_ID_NO_BACKSLASH_ESCAPES) == false && *p == '\\')
{
p++;
}
else if (*p == '\'')
{
if (*(p + 1) == '\'')
{
/* escape by '' */
p++;
}
else
{
state = CSQL_STATE_GENERAL;
}
}
break;
case CSQL_STATE_MYSQL_QUOTE:
if (prm_get_bool_value (PRM_ID_NO_BACKSLASH_ESCAPES) == false && *p == '\\')
{
p++;
}
else if (*p == '"')
{
if (*(p + 1) == '\"')
{
/* escape by "" */
p++;
}
else
{
state = CSQL_STATE_GENERAL;
}
}
break;
case CSQL_STATE_DOUBLE_QUOTE_IDENTIFIER:
if (*p == '"')
{
state = CSQL_STATE_GENERAL;
}
break;
case CSQL_STATE_BACKTICK_IDENTIFIER:
if (*p == '`')
{
state = CSQL_STATE_GENERAL;
}
break;
case CSQL_STATE_BRACKET_IDENTIFIER:
if (*p == ']')
{
state = CSQL_STATE_GENERAL;
}
break;
default:
/* should not be here */
break;
}
}
/* when include other stmts and the last smt is non sense stmt. */
if (include_stmt && !is_last_stmt_valid
&& (state == CSQL_STATE_SQL_COMMENT || state == CSQL_STATE_CPP_COMMENT || state == CSQL_STATE_GENERAL))
{
state = CSQL_STATE_STATEMENT_END;
}
csql_Edit_contents.state = state;
csql_Edit_contents.substate = substate;
csql_Edit_contents.plcsql_begin_end_balance = plcsql_begin_end_balance;
csql_Edit_contents.plcsql_nest_level = plcsql_nest_level;
}
/*
* csql_is_statement_complete () - check if end of statement is reached
* return : true if statement end is reached, false otherwise
*/
bool
csql_is_statement_complete (void)
{
if (csql_Edit_contents.state == CSQL_STATE_STATEMENT_END)
{
return true;
}
else
{
return false;
}
}
/*
* csql_is_statement_in_block () - check if statement state is string block or
* comment block or identifier block
* return : true if yes, false otherwise
*/
bool
csql_is_statement_in_block (void)
{
CSQL_STATEMENT_STATE state = csql_Edit_contents.state;
if (state == CSQL_STATE_C_COMMENT || state == CSQL_STATE_SINGLE_QUOTE || state == CSQL_STATE_MYSQL_QUOTE
|| state == CSQL_STATE_DOUBLE_QUOTE_IDENTIFIER || state == CSQL_STATE_BACKTICK_IDENTIFIER
|| state == CSQL_STATE_BRACKET_IDENTIFIER)
{
return true;
}
return false;
}
/*
* csql_edit_buffer_clear() - clear current editor contents
* return: none
* NOTE: allocated memory in csql_Edit_contents is not freed.
*/
void
csql_edit_contents_clear ()
{
csql_Edit_contents.data_size = 0;
csql_Edit_contents.state = CSQL_STATE_GENERAL;
csql_Edit_contents.substate = CSQL_SUBSTATE_INITIAL;
csql_Edit_contents.plcsql_begin_end_balance = 0;
csql_Edit_contents.plcsql_nest_level = 0;
}
void
csql_edit_contents_finalize ()
{
csql_edit_contents_clear ();
free_and_init (csql_Edit_contents.contents);
csql_Edit_contents.alloc_size = 0;
}
/*
* csql_edit_read_file() - read chars from the given file stream into
* current editor contents
* return: CSQL_FAILURE/CSQL_SUCCESS
* fp(in): file stream
*/
int
csql_edit_read_file (FILE * fp)
{
char line_buf[1024];
bool is_first_read_line = true;
while (fgets (line_buf, sizeof (line_buf), fp) != NULL)
{
char *line_begin = line_buf;
if (is_first_read_line && intl_is_bom_magic (line_buf, strlen (line_buf)))
{
line_begin += 3;
}
is_first_read_line = false;
if (csql_edit_contents_append (line_begin, false) != CSQL_SUCCESS)
return CSQL_FAILURE;
// to continue recognizing the end of PL/CSQL SP CREATE statements
// at the right recognizer status (state, substate, etc of csql_Edit_contents)
// after this editor session
csql_walk_statement (line_begin);
}
return CSQL_SUCCESS;
}
/*
* csql_edit_write_file() - write current editor contents to specified file
* return: CSQL_FAILURE/CSQL_SUCCESS
* fp(in): open file pointer
*/
int
csql_edit_write_file (FILE * fp)
{
char *p = csql_Edit_contents.contents;
int remain_size = csql_Edit_contents.data_size;
int write_len;
while (remain_size > 0)
{
write_len = (int) fwrite (p + (csql_Edit_contents.data_size - remain_size), 1, remain_size, fp);
if (write_len <= 0)
{
csql_Error_code = CSQL_ERR_OS_ERROR;
return CSQL_FAILURE;
}
remain_size -= write_len;
}
return CSQL_SUCCESS;
}
typedef struct
{
int error_code;
int msg_id;
} CSQL_ERR_MSG_MAP;
static CSQL_ERR_MSG_MAP csql_Err_msg_map[] = {
{CSQL_ERR_NO_MORE_MEMORY, CSQL_E_NOMOREMEMORY_TEXT},
{CSQL_ERR_TOO_LONG_LINE, CSQL_E_TOOLONGLINE_TEXT},
{CSQL_ERR_TOO_MANY_LINES, CSQL_E_TOOMANYLINES_TEXT},
{CSQL_ERR_TOO_MANY_FILE_NAMES, CSQL_E_TOOMANYFILENAMES_TEXT},
{CSQL_ERR_SESS_CMD_NOT_FOUND, CSQL_E_SESSCMDNOTFOUND_TEXT},
{CSQL_ERR_SESS_CMD_AMBIGUOUS, CSQL_E_SESSCMDAMBIGUOUS_TEXT},
{CSQL_ERR_FILE_NAME_MISSED, CSQL_E_FILENAMEMISSED_TEXT},
{CSQL_ERR_CUBRID_STMT_AMBIGUOUS, CSQL_E_CSQLCMDAMBIGUOUS_TEXT},
{CSQL_ERR_CANT_EXEC_PAGER, CSQL_E_CANTEXECPAGER_TEXT},
{CSQL_ERR_INVALID_ARG_COMBINATION, CSQL_E_INVALIDARGCOM_TEXT},
{CSQL_ERR_CANT_EDIT, CSQL_E_CANT_EDIT_TEXT},
{CSQL_ERR_INFO_CMD_HELP, CSQL_HELP_INFOCMD_TEXT},
{CSQL_ERR_CLASS_NAME_MISSED, CSQL_E_CLASSNAMEMISSED_TEXT},
{CSQL_ERR_FORMAT, CSQL_E_FORMAT_TEXT},
{CSQL_ERR_SYSTEM_CATALOG_COMPILE, CSQL_E_SYSTEM_CATALOG_COMPILE_FAIL_TEXT}
};
/*
* csql_errmsg() - return an error message string according to the given
* error code
* return: error message
* code(in): error code
*/
const char *
csql_errmsg (int code)
{
int msg_map_size;
const char *msg;
if (code == CSQL_ERR_OS_ERROR)
{
return (strerror (errno));
}
else if (code == CSQL_ERR_SQL_ERROR)
{
msg = db_error_string (DEFAULT_DB_ERROR_MSG_LEVEL);
return ((msg == NULL) ? "" : msg);
}
else
{
int i;
msg_map_size = DIM (csql_Err_msg_map);
for (i = 0; i < msg_map_size; i++)
{
if (code == csql_Err_msg_map[i].error_code)
{
return (csql_get_message (csql_Err_msg_map[i].msg_id));
}
}
return (csql_get_message (CSQL_E_UNKNOWN_TEXT));
}
}
static bool
is_identifier_letter (const char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_');
}
static bool
match_word_ci (const char *word, const char **bufp)
{
int len = strlen (word);
assert (len > 0);
if (strncasecmp (word, *bufp, len) == 0 && !is_identifier_letter ((*bufp)[len]))
{
*bufp += (len - 1); // advance the pointer to the last letter
return true;
}
else
{
return false;
}
}