Skip to content

File compactdb.c

File List > cubrid > src > executables > compactdb.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.
 *
 */

/*
 * compactdb.c: utility that compacts a database
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <assert.h>

#include "porting.h"
#include "dbtype.h"
#include "load_object.h"
#include "db.h"
#include "locator_cl.h"
#include "locator_sr.h"
#include "schema_manager.h"
#include "heap_file.h"
#include "system_catalog.h"
#include "object_accessor.h"
#include "set_object.h"
#include "btree.h"
#include "message_catalog.h"
#include "network_interface_cl.h"
#include "server_interface.h"
#include "system_parameter.h"
#include "utility.h"
#include "authenticate.h"
#include "transaction_cl.h"

#include "dbtype.h"
#include "thread_manager.hpp"

static int class_objects = 0;
static int total_objects = 0;
static int failed_objects = 0;
static RECDES *Diskrec = NULL;

static int compactdb_start (bool verbose_flag, char *input_filename, char **input_class_name, int input_class_length);
static void process_class (THREAD_ENTRY * thread_p, DB_OBJECT * class_, bool verbose_flag);
static void process_object (THREAD_ENTRY * thread_p, DESC_OBJ * desc_obj, OID * obj_oid, bool verbose_flag);
static int process_set (THREAD_ENTRY * thread_p, DB_SET * set);
static int process_value (THREAD_ENTRY * thread_p, DB_VALUE * value);
static DB_OBJECT *is_class (OID * obj_oid, OID * class_oid);
static int disk_update_instance (THREAD_ENTRY * thread_p, MOP classop, DESC_OBJ * obj, OID * oid);
static RECDES *alloc_recdes (int length);
static void free_recdes (RECDES * rec);
static void disk_init (void);
static void disk_final (void);
static int update_indexes (OID * class_oid, OID * obj_oid, RECDES * rec);
static void compact_usage (const char *argv0);

extern int get_class_mops (char **class_names, int num_class, MOP ** class_list, int *num_class_list);
extern int get_class_mops_from_file (const char *input_filename, MOP ** class_list, int *num_class_mops);

/*
 * compact_usage() - print an usage of the backup-utility
 *   return: void
 *   exec_name(in): a name of this application
 */
static void
compact_usage (const char *argv0)
{
  const char *exec_name;

  exec_name = basename ((char *) argv0);
  util_log_write_errid (MSGCAT_UTIL_GENERIC_INVALID_ARGUMENT);
  printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, 60), exec_name);
}

/*
 * compactdb - compactdb main routine
 *    return: 0 if successful, error code otherwise
 *    arg(in): a map of command line arguments
 */
int
compactdb (UTIL_FUNCTION_ARG * arg)
{
  UTIL_ARG_MAP *arg_map = arg->arg_map;
  const char *exec_name = arg->command_name;
  int error;
  int status = 0;
  const char *database_name;
  bool verbose_flag = 0;
  char *input_filename = NULL;
  char **tables = NULL;
  int table_size = 0;
  int i = 0;

  database_name = utility_get_option_string_value (arg_map, OPTION_STRING_TABLE, 0);
  verbose_flag = utility_get_option_bool_value (arg_map, COMPACT_VERBOSE_S);

  if (database_name == NULL || database_name[0] == '\0' || utility_get_option_string_table_size (arg_map) < 1)
    {
      compact_usage (arg->argv0);
      return ER_GENERIC_ERROR;
    }

  input_filename = utility_get_option_string_value (arg_map, COMPACT_INPUT_CLASS_FILE_S, 0);

  table_size = utility_get_option_string_table_size (arg_map);
  if (table_size > 1 && input_filename != NULL)
    {
      compact_usage (arg->argv0);
      return ER_GENERIC_ERROR;
    }
  else if (table_size > 1)
    {
      tables = (char **) malloc (sizeof (char *) * (table_size - 1));
      if (tables == NULL)
    {
      PRINT_AND_LOG_ERR_MSG (msgcat_message
                 (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_FAILURE));
      return ER_GENERIC_ERROR;
    }

      for (i = 1; i < table_size; i++)
    {
      tables[i - 1] = utility_get_option_string_value (arg_map, OPTION_STRING_TABLE, i);
    }
    }

  AU_DISABLE_PASSWORDS ();
  db_set_client_type (DB_CLIENT_TYPE_ADMIN_UTILITY);
  if ((error = db_login ("DBA", NULL)) || (error = db_restart (arg->argv0, TRUE, database_name)))
    {
      PRINT_AND_LOG_ERR_MSG ("%s: %s\n\n", exec_name, db_error_string (3));
      status = error;
    }
  else
    {
      status = compactdb_start (verbose_flag, input_filename, tables, table_size - 1);
      if (status != 0)
    {
      util_log_write_errstr ("%s\n", db_error_string (3));
    }
      if ((error = db_shutdown ()))
    {
      PRINT_AND_LOG_ERR_MSG ("%s: %s\n", exec_name, db_error_string (3));
      status = error;
    }
    }
  return status;
}

/*
 * compactdb_start - compact database
 *    return: 0 if successful, error code otherwise
 *    verbose_flag(in)
 */
static int
compactdb_start (bool verbose_flag, char *input_filename, char **input_class_names, int input_class_length)
{
  LIST_MOPS *class_table = NULL;
  int i;
  MOBJ object = NULL;
  HFID *hfid = NULL;
  int status = 0;
  THREAD_ENTRY *thread_p = NULL;
  MOP *class_mops = NULL;
  int num_class_mops = 0;
  bool skip_phase3 = false;

  /*
   * Build class name table
   */

  if (prm_get_integer_value (PRM_ID_COMPACTDB_PAGE_RECLAIM_ONLY) != 2)
    {
      if (input_filename && input_class_names && input_class_length > 0)
    {
      return ER_FAILED;
    }

      if (input_class_names && input_class_length > 0)
    {
      status = get_class_mops (input_class_names, input_class_length, &class_mops, &num_class_mops);
      if (status != NO_ERROR)
        {
          goto clean;
        }
      skip_phase3 = true;
    }
      else if (input_filename)
    {
      status = get_class_mops_from_file (input_filename, &class_mops, &num_class_mops);
      if (status != NO_ERROR)
        {
          goto clean;
        }
      skip_phase3 = true;
    }
      else
    {
      class_table = locator_get_all_mops (sm_Root_class_mop, DB_FETCH_QUERY_READ, NULL);
      if (class_table == NULL)
        {
          goto clean;   /* error */
        }
      num_class_mops = class_table->num;
      class_mops = class_table->mops;
    }
    }

  if (prm_get_integer_value (PRM_ID_COMPACTDB_PAGE_RECLAIM_ONLY) == 1)
    {
      goto phase2;
    }
  else if (prm_get_integer_value (PRM_ID_COMPACTDB_PAGE_RECLAIM_ONLY) == 2)
    {
      goto phase3;
    }

  if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_PASS1));
    }

  thread_p = thread_get_thread_entry_info ();

  /*
   * Dump the object definitions
   */
  disk_init ();
  for (i = 0; i < num_class_mops; i++)
    {
      if (!WS_IS_DELETED (class_mops[i]) && class_mops[i] != sm_Root_class_mop)
    {
      process_class (thread_p, class_mops[i], verbose_flag);
    }
    }
  disk_final ();

  db_commit_transaction ();

  if (failed_objects != 0)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_FAILED),
          total_objects - failed_objects, total_objects);
      status = 1;
      /*
       * TODO Processing should not continue in this case as we cannot be sure
       * that all references to deleted objects have been set to NULL. Most of
       * the code in the offline compactdb should be modified to check for
       * error conditions.
       */
    }
  else
    {
      if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_PROCESSED),
          total_objects);
    }
    }

phase2:
  if (verbose_flag)
    {
      printf ("\n");
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_PASS2));
    }

  for (i = 0; i < num_class_mops; i++)
    {
      ws_find (class_mops[i], &object);
      if (object == NULL)
    {
      continue;
    }

      if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_CLASS),
          sm_ch_name (object));
    }
      hfid = sm_ch_heap (object);
      if (hfid->vfid.fileid == NULL_FILEID)
    {
      continue;
    }
      (void) heap_reclaim_addresses (hfid);

    }

phase3:
  if (skip_phase3)
    {
      goto clean;
    }

  if (verbose_flag)
    {
      printf ("\n");
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_PASS3));
    }

  catalog_reclaim_space (thread_p);
  db_commit_transaction ();

  if (file_tracker_reclaim_marked_deleted (thread_p) != NO_ERROR)
    {
      /* how to handle error? */
      ASSERT_ERROR ();
      er_set (ER_NOTIFICATION_SEVERITY, ARG_FILE_LINE, ER_GENERIC_ERROR, 0);
    }
  db_commit_transaction ();

  /*
   * Cleanup
   */
clean:
  if (class_table)
    {
      locator_free_list_mops (class_table);
    }
  else
    {
      if (class_mops)
    {
      for (i = 0; i < num_class_mops; i++)
        {
          class_mops[i] = NULL;
        }

      free_and_init (class_mops);
    }
    }

  return status;
}


/*
 * process_class - dump objects for given class
 *    return: void
 *    cl_no(in): class table index
 *    verbose_flag(in)
 */
static void
process_class (THREAD_ENTRY * thread_p, DB_OBJECT * class_, bool verbose_flag)
{
  int i = 0;
  SM_CLASS *class_ptr;
  LC_COPYAREA *fetch_area;  /* Area where objects are received */
  HFID *hfid;
  OID *class_oid;
  OID last_oid;
  LOCK lock = S_LOCK;       /* Lock to acquire for the above purpose */
  int nobjects, nfetched;
  LC_COPYAREA_MANYOBJS *mobjs;  /* Describe multiple objects in area */
  LC_COPYAREA_ONEOBJ *obj;  /* Describe on object in area */
  RECDES recdes;        /* Record descriptor */
  DESC_OBJ *desc_obj;       /* The object described by obj */

  class_objects = 0;

  /* Get the class data */
  ws_find (class_, (MOBJ *) (&class_ptr));
  if (class_ptr == NULL)
    {
      return;
    }

  class_oid = ws_oid (class_);

  if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_CLASS),
          sm_ch_name ((MOBJ) class_ptr));
    }

#if defined(CUBRID_DEBUG)
  printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_OID), class_oid->volid,
      class_oid->pageid, class_oid->slotid);
#endif

  /* Find the heap where the instances are stored */
  hfid = sm_ch_heap ((MOBJ) class_ptr);
  if (hfid->vfid.fileid == NULL_FILEID)
    {
      if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_INSTANCES), 0);
    }
      return;
    }

  /* Flush all the instances */

  if (locator_flush_all_instances (class_, DONT_DECACHE) != NO_ERROR)
    {
      if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_INSTANCES), 0);
    }
      return;
    }

  nobjects = 0;
  nfetched = -1;
  OID_SET_NULL (&last_oid);

  /* Now start fetching all the instances */
  desc_obj = make_desc_obj (class_ptr, -1);
  while (nobjects != nfetched)
    {
      if (locator_fetch_all (hfid, &lock, LC_FETCH_MVCC_VERSION, class_oid, &nobjects, &nfetched, &last_oid,
                 &fetch_area, 1, -1, -1) == NO_ERROR)
    {
      if (fetch_area != NULL)
        {
          mobjs = LC_MANYOBJS_PTR_IN_COPYAREA (fetch_area);
          obj = LC_START_ONEOBJ_PTR_IN_COPYAREA (mobjs);

          for (i = 0; i < mobjs->num_objs; i++)
        {
          class_objects++;
          total_objects++;
          LC_RECDES_TO_GET_ONEOBJ (fetch_area, obj, &recdes);
          if (desc_disk_to_obj (class_, class_ptr, &recdes, desc_obj, false) == NO_ERROR)
            {
              process_object (thread_p, desc_obj, &obj->oid, verbose_flag);
            }
          else
            {
              failed_objects++;
            }

          obj = LC_NEXT_ONEOBJ_PTR_IN_COPYAREA (obj);
        }
          locator_free_copy_area (fetch_area);
        }
      else
        {
          /* No more objects */
          break;
        }
    }
    }
  desc_free (desc_obj);

  /*
   * Now that the class has been processed, we can reclaim space in the catalog
   * and the schema.
   * Might be able to ignore failed objects here if we're sure that's
   * indicative of a corrupted object.
   */
  if (failed_objects == 0)
    {
      if (catalog_drop_old_representations (thread_p, class_oid) == NO_ERROR)
    {
      (void) sm_destroy_representations (class_);
    }
    }
  /* else, should have some sort of warning message ? */

  if (verbose_flag)
    printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_INSTANCES), class_objects);

}

/*
 * process_object - process one object. update instance if needed
 *    return: void
 *    desc_obj(in): object data
 *    obj_oid(in): object oid
 *    recdes(in): record descriptor
 *    verbose_flag(in)
 */
static void
process_object (THREAD_ENTRY * thread_p, DESC_OBJ * desc_obj, OID * obj_oid, bool verbose_flag)
{
  SM_CLASS *class_ptr;
  SM_ATTRIBUTE *attribute;
  DB_VALUE *value;
  OID *class_oid;
  int v = 0;
  int update_flag = 0;

  class_ptr = desc_obj->class_;
  class_oid = ws_oid (desc_obj->classop);

#if defined(CUBRID_DEBUG)
  printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_OID), obj_oid->volid,
      obj_oid->pageid, obj_oid->slotid);
#endif

  attribute = class_ptr->attributes;
  while (attribute)
    {
      value = &desc_obj->values[v++];
      update_flag += process_value (thread_p, value);
      attribute = (SM_ATTRIBUTE *) attribute->header.next;
    }

  if ((desc_obj->updated_flag) || (update_flag))
    {
      if (verbose_flag)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_UPDATING));
    }
      disk_update_instance (thread_p, desc_obj->classop, desc_obj, obj_oid);
    }
}


/*
 * process_value - process one value
 *    return: whether the object should be updated.
 *    value(in): the value to process
 */
static int
process_value (THREAD_ENTRY * thread_p, DB_VALUE * value)
{
  int return_value = 0;

  switch (DB_VALUE_TYPE (value))
    {
    case DB_TYPE_OID:
    case DB_TYPE_OBJECT:
      {
    OID *ref_oid;
    SCAN_CODE scan_code;
    HEAP_SCANCACHE scan_cache;

    if (DB_VALUE_TYPE (value) == DB_TYPE_OID)
      {
        ref_oid = db_get_oid (value);
      }
    else
      {
        ref_oid = WS_OID (db_get_object (value));
      }

    if (OID_ISNULL (ref_oid))
      {
        break;
      }

    heap_scancache_quick_start (&scan_cache);
    scan_cache.mvcc_snapshot = logtb_get_mvcc_snapshot (thread_p);
    scan_code = heap_get_visible_version (thread_p, ref_oid, NULL, NULL, &scan_cache, PEEK, NULL_CHN);
    heap_scancache_end (thread_p, &scan_cache);

#if defined(CUBRID_DEBUG)
    printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_REFOID), ref_oid->volid,
        ref_oid->pageid, ref_oid->slotid, ref_class_oid.volid, ref_class_oid.pageid, ref_class_oid.slotid);
#endif

    if (scan_code != S_SUCCESS)
      {
        /* Set NULL link. */
        OID_SET_NULL (ref_oid);
        return_value = 1;
        break;
      }

    /* No updates */
    break;
      }

    case DB_TYPE_POINTER:
    case DB_TYPE_SET:
    case DB_TYPE_MULTISET:
    case DB_TYPE_SEQUENCE:
      {
    return_value = process_set (thread_p, db_get_set (value));
    break;
      }

    case DB_TYPE_NULL:
    case DB_TYPE_BLOB:
    case DB_TYPE_CLOB:
    default:
      break;
    }

  return return_value;
}


/*
 * process_set - process one set
 *    return: whether the object should be updated.
 *    set(in): the set to process
 */
static int
process_set (THREAD_ENTRY * thread_p, DB_SET * set)
{
  SET_ITERATOR *it;
  DB_VALUE *element_value;
  int return_value = 0;

  it = set_iterate (set);
  while ((element_value = set_iterator_value (it)) != NULL)
    {
      return_value += process_value (thread_p, element_value);
      set_iterator_next (it);
    }
  set_iterator_free (it);

  return return_value;
}


/*
 * is_class - determine whether the object is actually a class.
 *    return: return the MOP of the object, otherwise NULL
 *    obj_oid(in): the object oid
 *    class_oid(in): the class oid
 */
static DB_OBJECT *
is_class (OID * obj_oid, OID * class_oid)
{
  if (OID_EQ (class_oid, WS_OID (sm_Root_class_mop)))
    {
      return ws_mop (obj_oid, NULL);
    }
  return NULL;
}

/*
 * disk_update_instance - update object instance
 *    return: number of processed instance. 0 is error.
 *    thread_p(in): thread entry
 *    classop(in): class object
 *    obj(in): object instance
 *    oid(in): oid
 */
static int
disk_update_instance (THREAD_ENTRY * thread_p, MOP classop, DESC_OBJ * obj, OID * oid)
{
  HEAP_OPERATION_CONTEXT update_context;
  HFID *hfid;
  int save_newsize;
  bool has_indexes;

  assert (oid != NULL);

  Diskrec->length = 0;
  if (desc_obj_to_disk (obj, Diskrec, &has_indexes))
    {
      if (Diskrec->length >= 0) /* OID_ISTEMP */
    {
      return 0;
    }
      else
    {
      save_newsize = -Diskrec->length + DB_PAGESIZE;
      /* make the record larger */
      free_recdes (Diskrec);
      Diskrec = alloc_recdes (save_newsize);
      if (Diskrec == NULL)
        {
          return 0;
        }
      /* try one more time */
      if (desc_obj_to_disk (obj, Diskrec, &has_indexes))
        {
          printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_CANT_TRANSFORM));
          return (0);
        }
    }
    }

  hfid = sm_ch_heap ((MOBJ) (obj->class_));
  if (HFID_IS_NULL (hfid))
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_NO_HEAP));
      return (0);
    }

  if (has_indexes)
    {
      update_indexes (WS_OID (classop), oid, Diskrec);
    }

  heap_create_update_context (&update_context, hfid, oid, WS_OID (classop), Diskrec, NULL,
                  UPDATE_INPLACE_CURRENT_MVCCID);
  if (heap_update_logical (thread_p, &update_context) != NO_ERROR)
    {
      printf (msgcat_message (MSGCAT_CATALOG_UTILS, MSGCAT_UTIL_SET_COMPACTDB, COMPACTDB_MSG_CANT_UPDATE));
      return (0);
    }

  return 1;
}

/*
 * alloc_recdes - allocate a DECDES
 *    return: DECDES allocated
 *    length(in): additional length in addition to RECDES itself.
 */
static RECDES *
alloc_recdes (int length)
{
  RECDES *rec;

  if ((rec = (RECDES *) malloc (sizeof (RECDES) + length)) != NULL)
    {
      rec->area_size = length;
      rec->length = 0;
      rec->type = 0;
      rec->data = ((char *) rec) + sizeof (RECDES);
    }
  return rec;
}

/*
 * free_recdes - free a DECDES allocatd by alloc_recdes
 *    return: void
 *    rec(out): DECDES
 */
static void
free_recdes (RECDES * rec)
{
  free_and_init (rec);
}

/*
 * disk_init - initialize Diskrec file scope variable
 *    return: void
 */
static void
disk_init (void)
{
  Diskrec = alloc_recdes (DB_PAGESIZE * 4);
}

/*
 * disk_final - free Diskrec file scope variable
 *    return: void
 */
static void
disk_final (void)
{
  free_recdes (Diskrec);
  Diskrec = NULL;
}

/*
 * update_indexes - update index for given OID
 *    return: NO_ERROR or error code
 *    class_oid(in): class oid
 *    obj_oid(in): object oid
 *    rec(in): RECDES instance having new value
 */
static int
update_indexes (OID * class_oid, OID * obj_oid, RECDES * rec)
{
  RECDES oldrec = { 0, 0, 0, NULL };
  bool old_object;
  int success;
  HEAP_GET_CONTEXT context;
  HEAP_SCANCACHE scan_cache;

  (void) heap_scancache_quick_start (&scan_cache);
  heap_init_get_context (NULL, &context, obj_oid, class_oid, &oldrec, &scan_cache, COPY, NULL_CHN);

  old_object = (heap_get_last_version (NULL, &context) == S_SUCCESS);

  if (old_object)
    {
      /*
       * 4th arg -> give up setting updated attr info
       * for replication..
       * 9rd arg -> data or schema, 10th arg -> max repl. log or not
       */
      success =
    locator_update_index (NULL, rec, &oldrec, NULL, 0, obj_oid, class_oid, SINGLE_ROW_UPDATE,
                  (HEAP_SCANCACHE *) NULL, NULL);
    }
  else
    {
      success = ER_FAILED;
    }

  heap_clean_get_context (NULL, &context);
  (void) heap_scancache_end (NULL, &scan_cache);

  return success;
}