Skip to content

File shard_parser.c

File List > broker > shard_parser.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.
 *
 */


/*
 * shard_parser.c -
 *
 */

#ident "$Id$"


#include "shard_parser.h"
#include "shard_proxy_log.h"

#if defined(WINDOWS)
static const char *left_trim (const char *str);
#else /* WINDOWS */
static inline const char *left_trim (const char *str);
#endif /* !WINDOWS */

static int sp_init_sp_value (SP_VALUE * value_p);
static void sp_free_sp_value (SP_VALUE * value_p);
static int sp_make_sp_value (SP_VALUE * value_p, const char *pos, int length);
static int sp_make_string_sp_value (SP_VALUE * value_p, const char *pos, int length);
static int sp_make_int_sp_value_from_string (SP_VALUE * value_p, char *pos, int length);


static void sp_init_praser_hint_list (SP_PARSER_HINT_LIST * list);
static void sp_append_parser_hint_to_ctx (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p);
static int sp_parse_sql_internal (SP_PARSER_CTX * parser_p);

static void sp_free_parser_hint_from_ctx (SP_PARSER_CTX * parser_p);
static int sp_process_token (SP_PARSER_CTX * parser_p, SP_TOKEN token_type);
static int sp_get_bind_type_and_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p);
#if defined(WINDOWS)
static void sp_copy_cursor_to_prv (SP_PARSER_CTX * parser_p);
#else /* WINDOWS */
static inline void sp_copy_cursor_to_prv (SP_PARSER_CTX * parser_p);
#endif /* !WINDOWS */
static int sp_get_string_bind_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p);
static int sp_get_int_bind_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p);
static int sp_is_valid_hint (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p);
static bool sp_is_start_token (SP_TOKEN token);

SP_PARSER_CTX *
sp_create_parser (const char *sql_stmt)
{
  SP_PARSER_CTX *parser_p = (SP_PARSER_CTX *) malloc (sizeof (SP_PARSER_CTX));
  if (parser_p == NULL)
    {
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Not enough virtual memory. failed to malloc parser context. " "(size:%d). \n",
         sizeof (SP_PARSER_CTX));
      return NULL;
    }

  parser_p->sql_stmt = strdup (sql_stmt);
  if (parser_p->sql_stmt == NULL)
    {
      free (parser_p);
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Not enough virtual memory. failed to strdup sql statement. " "(sql_stmt:[%s]).",
         sql_stmt);
      return NULL;
    }
  parser_p->is_select = false;
  parser_p->bind_count = 0;
  parser_p->cursor.pos = parser_p->sql_stmt;
  parser_p->cursor.token = TT_NONE;
  parser_p->prv_cursor.pos = parser_p->sql_stmt;
  parser_p->prv_cursor.token = TT_NONE;
  parser_p->operator_ = TT_NONE;
  sp_init_praser_hint_list (&parser_p->list_a);
  sp_init_praser_hint_list (&parser_p->list_t[BT_STATIC]);
  sp_init_praser_hint_list (&parser_p->list_t[BT_DYNAMIC]);

  return parser_p;
}

int
sp_parse_sql (SP_PARSER_CTX * parser_p)
{
  int error = NO_ERROR;

  error = sp_parse_sql_internal (parser_p);
  if (error != NO_ERROR)
    {
      return error;
    }

  if (parser_p->cursor.token != TT_NONE)
    {
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Unexpected token type. " "(token_type:%d).", parser_p->cursor.token);
      return ER_SP_INVALID_SYNTAX;
    }

  return NO_ERROR;
}

void
sp_destroy_parser (SP_PARSER_CTX * parser_p)
{
  if (parser_p != NULL)
    {
      if (parser_p->sql_stmt != NULL)
    {
      free (parser_p->sql_stmt);
    }

      sp_free_parser_hint_from_ctx (parser_p);
      free (parser_p);
    }
}

bool
sp_is_hint_static (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_a.size == parser_p->list_t[BT_STATIC].size;
}

int
sp_get_total_hint_count (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_a.size;
}

int
sp_get_static_hint_count (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_t[BT_STATIC].size;
}

int
sp_get_dynamic_hint_count (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_t[BT_DYNAMIC].size;
}

SP_PARSER_HINT *
sp_get_first_hint (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_a.head;
}

SP_PARSER_HINT *
sp_get_next_hint (SP_PARSER_HINT * hint_p)
{
  return hint_p->next_a;
}

SP_PARSER_HINT *
sp_get_first_static_hint (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_t[BT_STATIC].head;
}

SP_PARSER_HINT *
sp_get_next_static_hint (SP_PARSER_HINT * hint_p)
{
  return hint_p->next_t[BT_STATIC];
}

SP_PARSER_HINT *
sp_get_first_dynamic_hint (SP_PARSER_CTX * parser_p)
{
  return parser_p->list_t[BT_DYNAMIC].head;
}

SP_PARSER_HINT *
sp_get_next_dynamic_hint (SP_PARSER_HINT * hint_p)
{
  return hint_p->next_t[BT_DYNAMIC];
}

char *
sp_get_hint_key (SP_PARSER_HINT * hint_p)
{
  return hint_p->arg.string.value;
}

const char *
sp_get_sql_stmt (SP_PARSER_CTX * parser_p)
{
  return parser_p->sql_stmt;
}

bool
sp_is_pair_token (SP_TOKEN start_token, SP_TOKEN end_token)
{
  switch (start_token)
    {
    case TT_NONE:
    case TT_WHITESPACE:
    case TT_NEWLINE:
    case TT_C_COMMENT_END:
    case TT_ASSIGN_OP:
    case TT_IN_OP:
    case TT_RIGHT_BRAKET:
    case TT_BIND_CHAR:
    case TT_SINGLE_QUOTED:
    case TT_DOUBLE_QUOTED:
      return start_token == end_token;
    case TT_LEFT_BRAKET:
      return end_token == TT_RIGHT_BRAKET;
    case TT_CSQL_COMMENT:
    case TT_CPP_COMMENT:
      return end_token == TT_NEWLINE;
    case TT_C_COMMENT:
    case TT_HINT:
    case TT_SHARD_HINT:
      return end_token == TT_C_COMMENT_END;
    }
  return false;
}

#if defined(WINDOWS)
static const char *
left_trim (const char *str)
#else /* WINDOWS */
static inline const char *
left_trim (const char *str)
#endif              /* !WINDOWS */
{
  const char *p = str;

  while (*p && isspace (*p))
    {
      p++;
    }

  return p;
}

static int
sp_init_sp_value (SP_VALUE * value_p)
{
  value_p->type = VT_INTEGER;
  value_p->string.length = 0;
  value_p->string.value = value_p->string.value_arr;
  value_p->string.value_arr[0] = '\0';
  value_p->string.value_ex = NULL;
  value_p->integer = 0;
  return NO_ERROR;
}

static void
sp_free_sp_value (SP_VALUE * value_p)
{
  if (value_p != NULL && value_p->string.value_ex != NULL)
    {
      free (value_p->string.value_ex);
    }
}

static int
sp_make_sp_value (SP_VALUE * value_p, const char *pos, int length)
{
  SP_TOKEN token;
  char *p = (char *) left_trim (pos);
  while (isspace (p[length - 1]))
    {
      length--;
    }

  sp_get_token_type (p, &token);
  if (token == TT_SINGLE_QUOTED || token == TT_DOUBLE_QUOTED)
    {
      return sp_make_string_sp_value (value_p, p + 1, length - 2);
    }
  else
    {
      return sp_make_int_sp_value_from_string (value_p, p, length);
    }
}

static int
sp_make_string_sp_value (SP_VALUE * value_p, const char *pos, int length)
{
  if (length <= 0)
    {
      return ER_SP_INVALID_HINT;
    }

  if (length > SP_VALUE_INIT_SIZE)
    {
      value_p->string.value_ex = (char *) malloc (sizeof (char) * (length + 1));
      if (value_p->string.value_ex == NULL)
    {
      return ER_SP_OUT_OF_MEMORY;
    }
      value_p->string.value = value_p->string.value_ex;
    }
  memcpy (value_p->string.value, pos, length);
  value_p->type = VT_STRING;
  value_p->string.length = length;
  value_p->string.value[length] = '\0';
  return NO_ERROR;
}

static int
sp_make_int_sp_value_from_string (SP_VALUE * value_p, char *pos, int length)
{
  int result = 0;
  char shard_key[SHARD_KEY_LENGTH + 1];
  int key_len = (length > SHARD_KEY_LENGTH) ? SHARD_KEY_LENGTH : length;
  char *p;

  memcpy (shard_key, pos, key_len);
  shard_key[key_len] = '\0';

  p = strchr (shard_key, ';');
  if (p)
    {
      *p = '\0';
    }

  result = parse_bigint (&value_p->integer, shard_key, 10);
  if (result != 0)
    {
      return ER_SP_INVALID_HINT;
    }

  value_p->type = VT_INTEGER;
  return NO_ERROR;
}

SP_PARSER_HINT *
sp_create_parser_hint (void)
{
  int error = NO_ERROR;
  SP_PARSER_HINT *hint_p = NULL;

  hint_p = (SP_PARSER_HINT *) malloc (sizeof (SP_PARSER_HINT));
  if (hint_p == NULL)
    {
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Not enough virtual memory. failed to malloc parser hint. " "(size:%d).",
         sizeof (SP_PARSER_HINT));

      return NULL;
    }

  error = sp_init_sp_value (&hint_p->arg);
  if (error != NO_ERROR)
    {
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Failed to initialize parser argument. " "(error:%d).", error);
      goto PARSER_ERROR;
    }

  error = sp_init_sp_value (&hint_p->value);
  if (error != NO_ERROR)
    {
      PROXY_LOG (PROXY_LOG_MODE_ERROR, "Failed to initialize parser value. " "(error:%d). \n", error);

      goto PARSER_ERROR;
    }

  hint_p->next_a = NULL;
  hint_p->next_t[BT_STATIC] = NULL;
  hint_p->next_t[BT_DYNAMIC] = NULL;
  hint_p->hint_type = HT_NONE;
  hint_p->bind_type = BT_STATIC;
  hint_p->bind_position = -1 /* INVALID POSITION */ ;
  return hint_p;
PARSER_ERROR:
  if (hint_p != NULL)
    {
      sp_free_parser_hint (hint_p);
      free (hint_p);
    }
  return NULL;
}

static void
sp_init_praser_hint_list (SP_PARSER_HINT_LIST * list)
{
  list->head = NULL;
  list->tail = NULL;
  list->size = 0;
}

static void
sp_append_parser_hint_to_ctx (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p)
{
  SP_BIND_TYPE type = hint_p->bind_type;

  parser_p->list_a.size++;
  parser_p->list_t[hint_p->bind_type].size++;

  if (parser_p->list_a.head == NULL)
    {
      parser_p->list_a.head = hint_p;
    }
  if (parser_p->list_t[type].head == NULL)
    {
      parser_p->list_t[type].head = hint_p;
    }
  if (parser_p->list_a.tail != NULL)
    {
      parser_p->list_a.tail->next_a = hint_p;
    }
  if (parser_p->list_t[type].tail != NULL)
    {
      parser_p->list_t[type].tail->next_t[type] = hint_p;
    }

  parser_p->list_a.tail = hint_p;
  parser_p->list_t[type].tail = hint_p;
}

void
sp_free_parser_hint (SP_PARSER_HINT * hint_p)
{
  if (hint_p != NULL)
    {
      sp_free_sp_value (&hint_p->arg);
      sp_free_sp_value (&hint_p->value);
    }
}

static int
sp_parse_sql_internal (SP_PARSER_CTX * parser_p)
{
  int error = NO_ERROR;
  SP_TOKEN token_type = TT_NONE;

  while (*parser_p->cursor.pos)
    {
      parser_p->cursor.pos = sp_get_token_type (parser_p->cursor.pos, &token_type);
      if (token_type == TT_NONE)
    {
      continue;
    }

      if (parser_p->cursor.token == TT_NONE)
    {
      if (sp_is_start_token (token_type) == false)
        {
          return ER_SP_INVALID_SYNTAX;
        }

      parser_p->cursor.token = token_type;

      error = sp_process_token (parser_p, token_type);
      if (error != NO_ERROR)
        {
          return error;
        }

      if (!sp_is_exist_pair_token (parser_p->cursor.token))
        {
          sp_copy_cursor_to_prv (parser_p);
        }
    }
      else if (sp_is_pair_token (parser_p->cursor.token, token_type))
    {
      sp_copy_cursor_to_prv (parser_p);
    }
    }

  if (parser_p->cursor.token == TT_CSQL_COMMENT || parser_p->cursor.token == TT_CPP_COMMENT)
    {
      parser_p->cursor.token = TT_NONE;
    }

  return NO_ERROR;
}

static void
sp_free_parser_hint_from_ctx (SP_PARSER_CTX * parser_p)
{
  SP_PARSER_HINT *hint_p = sp_get_first_hint (parser_p);
  SP_PARSER_HINT *hint_np = NULL;

  while (hint_p != NULL)
    {
      hint_np = sp_get_next_hint (hint_p);
      sp_free_parser_hint (hint_p);
      free (hint_p);
      hint_p = hint_np;
    }
}

const char *
sp_get_token_type (const char *sql, SP_TOKEN * token)
{
  const char *p = sql;

  *token = TT_NONE;

  switch (*(p++))
    {
    case '\'':
      *token = TT_SINGLE_QUOTED;
      break;
    case '"':
      *token = TT_DOUBLE_QUOTED;
      break;
    case '\\':
      /* escape char */
      p += 1;
      break;
    case '/':
      switch (*p)
    {
    case '/':
      p += 1;
      *token = TT_CPP_COMMENT;
      break;
    case '*':
      if (*(p + 1) == '+')
        {
          p += 2;
          *token = TT_HINT;
        }
      else
        {
          p += 1;
          *token = TT_C_COMMENT;
        }
      break;
    }
      break;
    case '-':
      if (*p == '-')
    {
      p += 1;
      *token = TT_CSQL_COMMENT;
    }
      break;
    case '*':
      if (*p == '/')
    {
      p += 1;
      *token = TT_C_COMMENT_END;
    }
      break;
    case '=':
      *token = TT_ASSIGN_OP;
      break;
    case '\n':
      *token = TT_NEWLINE;
      break;
    case ' ':
    case '\f':
    case '\r':
    case '\t':
    case '\v':
      *token = TT_WHITESPACE;
      if (tolower (*p) == 'i' && tolower (*(p + 1)) == 'n' && (isspace (*(p + 2)) || *(p + 2) == '('))
    {
      p += 3;
      *token = TT_IN_OP;
    }
      break;
    case '?':
      *token = TT_BIND_CHAR;
      break;
    case '(':
      *token = TT_LEFT_BRAKET;
      break;
    case ')':
      *token = TT_RIGHT_BRAKET;
      break;
    }

  return p;
}

const char *
sp_get_hint_type (const char *sql, SP_HINT_TYPE * hint_type)
{
  sql = left_trim (sql);

  *hint_type = HT_NONE;
  if (strncasecmp (sql, "shard_", 6) != 0)
    {
      return sql;
    }

  sql += 6;
  if (strncasecmp (sql, "key", 3) == 0)
    {
      sql += 3;
      *hint_type = HT_KEY;
    }
  else if (strncasecmp (sql, "val", 3) == 0)
    {
      sql += 3;
      *hint_type = HT_VAL;
    }
  else if (strncasecmp (sql, "all", 3) == 0)
    {
      sql += 3;
      *hint_type = HT_ALL;
    }
  else if (strncasecmp (sql, "id", 2) == 0)
    {
      sql += 2;
      *hint_type = HT_ID;
    }
  return sql;
}

const char *
sp_get_hint_arg (const char *sql, SP_PARSER_HINT * hint_p, int *error)
{
  SP_TOKEN start_token;
  SP_TOKEN end_token;
  const char *p, *q;

  *error = ER_SP_INVALID_HINT;

  sql = left_trim (sql);
  sp_get_token_type (sql, &start_token);

  if (start_token != TT_LEFT_BRAKET)
    {
      if (hint_p->hint_type != HT_VAL && hint_p->hint_type != HT_ID)
    {
      *error = NO_ERROR;
    }
      return sql;
    }

  sql = left_trim (sql + 1);
  q = p = sql;

  while (*p)
    {
      q = sp_get_token_type (p, &end_token);
      if (sp_is_pair_token (start_token, end_token))
    {
      *error = sp_make_sp_value (&hint_p->arg, sql, p - sql);
      return q;
    }
      p = q;
    }

  *error = ER_SP_INVALID_HINT;
  return sql;
}

const char *
sp_check_end_of_hint (const char *sql, int *error)
{
  sql = left_trim (sql);

  if (*(sql++) == '*' && *(sql++) == '/')
    {
      *error = NO_ERROR;
    }
  else
    {
      *error = ER_SP_INVALID_HINT;
    }

  return sql;
}

static int
sp_process_token (SP_PARSER_CTX * parser_p, SP_TOKEN token_type)
{
  int error = NO_ERROR;
  SP_PARSER_HINT *hint_p = NULL;
  SP_HINT_TYPE hint_type = HT_NONE;

  switch (token_type)
    {
    case TT_BIND_CHAR:
      parser_p->bind_count++;
      break;
    case TT_HINT:
      parser_p->cursor.pos = sp_get_hint_type (parser_p->cursor.pos, &hint_type);
      if (hint_type == HT_NONE)
    {
      return NO_ERROR;
    }
      parser_p->cursor.token = TT_SHARD_HINT;

      hint_p = sp_create_parser_hint ();
      if (hint_p == NULL)
    {
      error = ER_SP_OUT_OF_MEMORY;
      goto PARSER_ERROR;
    }
      hint_p->hint_type = hint_type;

      parser_p->cursor.pos = sp_get_hint_arg (parser_p->cursor.pos, hint_p, &error);
      if (error != NO_ERROR)
    {
      goto PARSER_ERROR;
    }

      parser_p->cursor.pos = sp_check_end_of_hint (parser_p->cursor.pos, &error);
      if (error != NO_ERROR)
    {
      goto PARSER_ERROR;
    }

      error = sp_is_valid_hint (parser_p, hint_p);
      if (error != NO_ERROR)
    {
      goto PARSER_ERROR;
    }

      if (hint_p->hint_type == HT_KEY)
    {
      error = sp_get_bind_type_and_value (parser_p, hint_p);
      if (error != NO_ERROR)
        {
          goto PARSER_ERROR;
        }
    }

      if (parser_p->operator_ == TT_ASSIGN_OP)
    {
      parser_p->operator_ = TT_NONE;
    }

      sp_append_parser_hint_to_ctx (parser_p, hint_p);
      break;
    case TT_IN_OP:
    case TT_ASSIGN_OP:
      parser_p->operator_ = token_type;
      break;
    case TT_RIGHT_BRAKET:
      parser_p->operator_ = TT_NONE;
      break;
    default:
      break;
    }

  return error;
PARSER_ERROR:
  if (hint_p != NULL)
    {
      sp_free_parser_hint (hint_p);
      free (hint_p);
    }
  return error;
}

static int
sp_get_bind_type_and_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p)
{
  int error = NO_ERROR;

  parser_p->cursor.pos = left_trim (parser_p->cursor.pos);

  switch (*parser_p->cursor.pos)
    {
    case '?':
      hint_p->bind_type = BT_DYNAMIC;
      hint_p->bind_position = parser_p->bind_count;
      parser_p->bind_count++;
      parser_p->cursor.pos++;
      break;
    case '\'':
    case '"':
      error = sp_get_string_bind_value (parser_p, hint_p);
      parser_p->cursor.pos++;
      break;
    default:
      error = sp_get_int_bind_value (parser_p, hint_p);
      break;
    }

  if (error != NO_ERROR)
    {
      return error;
    }

  return error;
}

#if defined(WINDOWS)
static void
sp_copy_cursor_to_prv (SP_PARSER_CTX * parser_p)
#else /* WINDOWS */
static inline void
sp_copy_cursor_to_prv (SP_PARSER_CTX * parser_p)
#endif              /* !WINDOWS */
{
  parser_p->prv_cursor = parser_p->cursor;
  parser_p->cursor.token = TT_NONE;
}

static int
sp_get_string_bind_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p)
{
  char *p;

  if (parser_p->cursor.pos == NULL)
    {
      return ER_SP_INVALID_SYNTAX;
    }
  p = (char *) parser_p->cursor.pos++;

  while (*parser_p->cursor.pos && *parser_p->cursor.pos != *p)
    {
      parser_p->cursor.pos++;
    }

  return sp_make_string_sp_value (&hint_p->value, p + 1, parser_p->cursor.pos - p - 1);
}

static int
sp_get_int_bind_value (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p)
{
  char *p = (char *) parser_p->cursor.pos;
  char *s = p;
  while (*s && (!isspace (*s) && *s != ',' && *s != ')'))
    {
      s++;
    }

  parser_p->cursor.pos = s;
  return sp_make_int_sp_value_from_string (&hint_p->value, p, parser_p->cursor.pos - p);
}

static int
sp_is_valid_hint (SP_PARSER_CTX * parser_p, SP_PARSER_HINT * hint_p)
{
  if (parser_p->is_select)
    {
      if (hint_p->hint_type == HT_KEY && parser_p->operator_ != TT_ASSIGN_OP && parser_p->operator_ != TT_IN_OP)
    {
      return ER_SP_INVALID_SYNTAX;
    }
    }
  return NO_ERROR;
}

static bool
sp_is_start_token (SP_TOKEN token)
{
  switch (token)
    {
    case TT_C_COMMENT_END:
      return false;
    default:
      return true;
    }
}

bool
sp_is_exist_pair_token (SP_TOKEN token)
{
  switch (token)
    {
    case TT_NONE:
    case TT_WHITESPACE:
    case TT_NEWLINE:
    case TT_C_COMMENT_END:
    case TT_ASSIGN_OP:
    case TT_IN_OP:
    case TT_LEFT_BRAKET:
    case TT_RIGHT_BRAKET:
    case TT_BIND_CHAR:
    case TT_SHARD_HINT:
      return false;
    default:
      return true;
    }
}