static uint get_table_structure()

in client/mysqldump.cc [3118:3722]


static uint get_table_structure(const char *table, char *db, char *table_type,
                                char *ignore_flag, bool real_columns[],
                                std::string *column_list) {
  bool init = false, write_data, complete_insert, skip_ddl;
  uint64_t num_fields;
  const char *result_table, *opt_quoted_table;
  const char *insert_option;
  char name_buff[NAME_LEN + 3], table_buff[NAME_LEN * 2 + 3];
  char table_buff2[NAME_LEN * 2 + 3], query_buff[QUERY_LENGTH];
  const char *show_fields_stmt =
      "SELECT `COLUMN_NAME` AS `Field`, "
      "`COLUMN_TYPE` AS `Type`, "
      "`IS_NULLABLE` AS `Null`, "
      "`COLUMN_KEY` AS `Key`, "
      "`COLUMN_DEFAULT` AS `Default`, "
      "`EXTRA` AS `Extra`, "
      "`COLUMN_COMMENT` AS `Comment` "
      "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE "
      "TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' "
      "ORDER BY ORDINAL_POSITION";
  FILE *sql_file = md_result_file;
  bool is_log_table;
  bool is_replication_metadata_table;
  bool is_view(false);
  unsigned int colno;
  MYSQL_RES *result;
  MYSQL_ROW row;
  DBUG_TRACE;
  DBUG_PRINT("enter", ("db: %s  table: %s", db, table));

  *ignore_flag = check_if_ignore_table(table, table_type);

  is_view = strcmp(table_type, "VIEW") == 0;

  if (opt_ignore_views && is_view) {
    DBUG_PRINT("exit", ("Dumping view ignored."));
    return 0;
  }

  /*
    for mysql.innodb_table_stats, mysql.innodb_index_stats tables we
    dont dump DDL
  */
  skip_ddl = innodb_stats_tables(db, table);

  complete_insert = false;
  if ((write_data = !(*ignore_flag & IGNORE_DATA))) {
    complete_insert = opt_complete_insert;
    if (!insert_pat_inited) {
      insert_pat_inited = true;
      init_dynamic_string_checked(&insert_pat, "", 1024);
    } else
      dynstr_set_checked(&insert_pat, "");
  }

  insert_option = ((opt_ignore || skip_ddl) ? "IGNORE " : "");

  verbose_msg("-- Retrieving table structure for table %s...\n", table);

  snprintf(query_buff, sizeof(query_buff), "SET SQL_QUOTE_SHOW_CREATE=%d",
           (opt_quoted || opt_keywords));

  result_table = quote_name(table, table_buff, true);
  opt_quoted_table = quote_name(table, table_buff2, false);

  if (!opt_xml && !mysql_query_with_error_report(mysql, nullptr, query_buff)) {
    /* using SHOW CREATE statement */
    if (!opt_no_create_info && !skip_ddl) {
      /* Make an sql-file, if path was given iow. option -T was given */
      char buff[20 + FN_REFLEN];
      MYSQL_FIELD *field;

      snprintf(buff, sizeof(buff), "show create table %s", result_table);

      if (switch_character_set_results(mysql, "binary") ||
          mysql_query_with_error_report(mysql, &result, buff) ||
          switch_character_set_results(mysql, default_charset))
        return 0;

      if (path) {
        if (!(sql_file = open_sql_file_for_table(table, O_WRONLY))) return 0;

        write_header(sql_file, db);
      }

      bool freemem = false;
      char const *text = fix_identifier_with_newline(result_table, &freemem);
      if (is_view) /* view */
        print_comment(sql_file, false,
                      "\n--\n-- Temporary view structure for view %s\n--\n\n",
                      text);
      else
        print_comment(sql_file, false,
                      "\n--\n-- Table structure for table %s\n--\n\n", text);
      if (freemem) my_free(const_cast<char *>(text));

      if (opt_drop) {
        /*
          Even if the "table" is a view, we do a DROP TABLE here.  The
          view-specific code below fills in the DROP VIEW.
          We will skip the DROP TABLE for general_log and slow_log, since
          those stmts will fail, in case we apply dump by enabling logging.
          We will skip this for replication metadata tables as well.
         */
        if (!(general_log_or_slow_log_tables(db, table) ||
              replication_metadata_tables(db, table)))
          fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", opt_quoted_table);
        check_io(sql_file);
      }

      field = mysql_fetch_field_direct(result, 0);
      if (strcmp(field->name, "View") == 0) {
        char *scv_buff = nullptr;
        uint64_t n_cols;

        verbose_msg("-- It's a view, create dummy view\n");

        /* save "show create" statement for later */
        if ((row = mysql_fetch_row(result)) && (scv_buff = row[1]))
          scv_buff = my_strdup(PSI_NOT_INSTRUMENTED, scv_buff, MYF(0));

        mysql_free_result(result);

        /*
          Create a table with the same name as the view and with columns of
          the same name in order to satisfy views that depend on this view.
          The table will be removed when the actual view is created.

          The properties of each column, are not preserved in this temporary
          table, because they are not necessary.

          This will not be necessary once we can determine dependencies
          between views and can simply dump them in the appropriate order.
        */
        snprintf(query_buff, sizeof(query_buff), "SHOW FIELDS FROM %s",
                 result_table);
        if (switch_character_set_results(mysql, "binary") ||
            mysql_query_with_error_report(mysql, &result, query_buff) ||
            switch_character_set_results(mysql, default_charset)) {
          /*
            View references invalid or privileged table/col/fun (err 1356),
            so we cannot create a stand-in table.  Be defensive and dump
            a comment with the view's 'show create' statement. (Bug #17371)
          */

          if (mysql_errno(mysql) == ER_VIEW_INVALID)
            fprintf(sql_file, "\n-- failed on view %s: %s\n\n", result_table,
                    scv_buff ? scv_buff : "");

          my_free(scv_buff);

          return 0;
        }
        my_free(scv_buff);

        n_cols = mysql_num_rows(result);
        if (0 != n_cols) {
          /*
            The actual formula is based on the column names and how the .FRM
            files are stored and is too volatile to be repeated here.
            Thus we simply warn the user if the columns exceed a limit we
            know works most of the time.
          */
          if (n_cols >= 1000)
            fprintf(stderr,
                    "-- Warning: Creating a stand-in table for view %s may"
                    " fail when replaying the dump file produced because "
                    "of the number of columns exceeding 1000. Exercise "
                    "caution when replaying the produced dump file.\n",
                    table);
          if (opt_drop) {
            /*
              We have already dropped any table of the same name above, so
              here we just drop the view.
            */

            fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n",
                    opt_quoted_table);
            check_io(sql_file);
          }

          fprintf(sql_file,
                  "SET @saved_cs_client     = @@character_set_client;\n"
                  "/*!50503 SET character_set_client = utf8mb4 */;\n"
                  "/*!50001 CREATE VIEW %s AS SELECT \n",
                  result_table);

          /*
            Get first row, following loop will prepend comma - keeps from
            having to know if the row being printed is last to determine if
            there should be a _trailing_ comma.
          */

          row = mysql_fetch_row(result);

          /*
            A temporary view is created to resolve the view interdependencies.
            This temporary view is dropped when the actual view is created.
          */

          fprintf(sql_file, " 1 AS %s", quote_name(row[0], name_buff, false));

          while ((row = mysql_fetch_row(result))) {
            fprintf(sql_file, ",\n 1 AS %s",
                    quote_name(row[0], name_buff, false));
          }

          fprintf(sql_file,
                  "*/;\n"
                  "SET character_set_client = @saved_cs_client;\n");

          check_io(sql_file);
        }

        mysql_free_result(result);

        if (path) my_fclose(sql_file, MYF(MY_WME));

        seen_views = true;
        return 0;
      }

      row = mysql_fetch_row(result);

      is_log_table = general_log_or_slow_log_tables(db, table);
      is_replication_metadata_table = replication_metadata_tables(db, table);
      if (is_log_table || is_replication_metadata_table)
        row[1] += 13; /* strlen("CREATE TABLE ")= 13 */

      fprintf(sql_file,
              "/*!40101 SET @saved_cs_client     = @@character_set_client */;\n"
              "/*!50503 SET character_set_client = utf8mb4 */;\n"
              "%s%s;\n"
              "/*!40101 SET character_set_client = @saved_cs_client */;\n",
              (is_log_table || is_replication_metadata_table)
                  ? "CREATE TABLE IF NOT EXISTS "
                  : "",
              row[1]);

      check_io(sql_file);
      mysql_free_result(result);
    }
    snprintf(query_buff, sizeof(query_buff), "show fields from %s",
             result_table);
    if (mysql_query_with_error_report(mysql, &result, query_buff)) {
      if (path) my_fclose(sql_file, MYF(MY_WME));
      return 0;
    }

    bool has_invisible_columns = false;
    if (write_data) {
      while ((row = mysql_fetch_row(result))) {
        if (row[SHOW_EXTRA]) {
          /*
            If data contents of table are to be written and option to prepare
            INSERT statement with complete column list is not set then scan the
            column list for generated columns and invisible columns. Presence
            of any generated column or invisible column will require that an
            explicit list of columns is printed for INSERT statements.
          */
          bool is_generated_column = false;
          if (strcmp(row[SHOW_EXTRA], "STORED GENERATED") == 0) {
            is_generated_column = true;
          } else if (strcmp(row[SHOW_EXTRA], "STORED GENERATED INVISIBLE") ==
                     0) {
            is_generated_column = true;
            has_invisible_columns |= true;
          } else if (strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED") == 0) {
            is_generated_column = true;
          } else if (strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED INVISIBLE") ==
                     0) {
            is_generated_column = true;
            has_invisible_columns |= true;
          } else if (!has_invisible_columns &&
                     (strstr(row[SHOW_EXTRA], "INVISIBLE") != nullptr)) {
            /*
              For timestamp and datetime type columns, EXTRA column might
              contain DEFAULT_GENERATED and 'on update CURRENT TIMESTAMP'.
              INVISIBLE keyword is appended at the end if column is invisible.
              So finding INVISIBLE keyword in EXTRA column to check column is
              invisible.
            */
            has_invisible_columns = true;
          }

          complete_insert |= (has_invisible_columns || is_generated_column);
        }
      }
      mysql_free_result(result);

      if (mysql_query_with_error_report(mysql, &result, query_buff)) {
        if (path) my_fclose(sql_file, MYF(MY_WME));
        return 0;
      }
    }
    /*
      If write_data is true, then we build up insert statements for
      the table's data. Note: in subsequent lines of code, this test
      will have to be performed each time we are appending to
      insert_pat.
    */
    if (write_data) {
      if (opt_replace_into)
        dynstr_append_checked(&insert_pat, "REPLACE ");
      else
        dynstr_append_checked(&insert_pat, "INSERT ");
      dynstr_append_checked(&insert_pat, insert_option);
      dynstr_append_checked(&insert_pat, "INTO ");
      dynstr_append_checked(&insert_pat, opt_quoted_table);
      if (complete_insert) {
        dynstr_append_checked(&insert_pat, " (");
      } else {
        dynstr_append_checked(&insert_pat, " VALUES ");
        if (!extended_insert) dynstr_append_checked(&insert_pat, "(");
      }
    }

    colno = 0;
    while ((row = mysql_fetch_row(result))) {
      if (row[SHOW_EXTRA]) {
        real_columns[colno] =
            (strcmp(row[SHOW_EXTRA], "STORED GENERATED") != 0 &&
             strcmp(row[SHOW_EXTRA], "STORED GENERATED INVISIBLE") != 0 &&
             strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED") != 0 &&
             strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED INVISIBLE") != 0);
      } else
        real_columns[colno] = true;

      if (has_invisible_columns && column_list != nullptr) {
        if (!column_list->empty()) column_list->append(", ");
        column_list->append(quote_name(row[SHOW_FIELDNAME], name_buff, false));
      }

      if (real_columns[colno++] && complete_insert) {
        if (init) dynstr_append_checked(&insert_pat, ", ");
        init = true;
        dynstr_append_checked(
            &insert_pat, quote_name(row[SHOW_FIELDNAME], name_buff, false));
      }
    }
    num_fields = mysql_num_rows(result);
    mysql_free_result(result);
  } else {
    verbose_msg("%s: Warning: Can't set SQL_QUOTE_SHOW_CREATE option (%s)\n",
                my_progname, mysql_error(mysql));

    snprintf(query_buff, sizeof(query_buff), show_fields_stmt, db, table);

    if (mysql_query_with_error_report(mysql, &result, query_buff)) return 0;

    bool has_invisible_columns = false;
    if (write_data) {
      while ((row = mysql_fetch_row(result))) {
        if (row[SHOW_EXTRA]) {
          /*
            If data contents of table are to be written and option to prepare
            INSERT statement with complete column list is not set then scan the
            column list for generated columns and invisible columns. Presence
            of any generated column or invisible column will require that an
            explicit list of columns is printed for INSERT statements.
          */
          bool is_generated_column = false;
          if (strcmp(row[SHOW_EXTRA], "STORED GENERATED") == 0) {
            is_generated_column = true;
          } else if (strcmp(row[SHOW_EXTRA], "STORED GENERATED INVISIBLE") ==
                     0) {
            is_generated_column = true;
            has_invisible_columns |= true;
          } else if (strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED") == 0) {
            is_generated_column = true;
          } else if (strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED INVISIBLE") ==
                     0) {
            is_generated_column = true;
            has_invisible_columns |= true;
          } else if (!has_invisible_columns &&
                     (strstr(row[SHOW_EXTRA], "INVISIBLE") != nullptr)) {
            /*
              For timestamp and datetime type columns, EXTRA column might
              contain DEFAULT_GENERATED and 'on update CURRENT TIMESTAMP'.
              INVISIBLE keyword is appended at the end if column is invisible.
              So finding INVISIBLE keyword in EXTRA column to check column is
              invisible.
            */
            has_invisible_columns = true;
          }

          complete_insert |= (has_invisible_columns || is_generated_column);
        }
      }
      mysql_free_result(result);

      if (mysql_query_with_error_report(mysql, &result, query_buff)) {
        if (path) my_fclose(sql_file, MYF(MY_WME));
        return 0;
      }
    }
    /* Make an sql-file, if path was given iow. option -T was given */
    if (!opt_no_create_info) {
      if (path) {
        if (!(sql_file = open_sql_file_for_table(table, O_WRONLY))) return 0;
        write_header(sql_file, db);
      }

      bool freemem = false;
      char const *text = fix_identifier_with_newline(result_table, &freemem);
      print_comment(sql_file, false,
                    "\n--\n-- Table structure for table %s\n--\n\n", text);
      if (freemem) my_free(const_cast<char *>(text));

      if (opt_drop)
        fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", result_table);
      if (!opt_xml)
        fprintf(sql_file, "CREATE TABLE %s (\n", result_table);
      else
        print_xml_tag(sql_file, "\t", "\n", "table_structure", "name=", table,
                      NullS);
      check_io(sql_file);
    }

    if (write_data) {
      if (opt_replace_into)
        dynstr_append_checked(&insert_pat, "REPLACE ");
      else
        dynstr_append_checked(&insert_pat, "INSERT ");
      dynstr_append_checked(&insert_pat, insert_option);
      dynstr_append_checked(&insert_pat, "INTO ");
      dynstr_append_checked(&insert_pat, result_table);
      if (complete_insert)
        dynstr_append_checked(&insert_pat, " (");
      else {
        dynstr_append_checked(&insert_pat, " VALUES ");
        if (!extended_insert) dynstr_append_checked(&insert_pat, "(");
      }
    }

    colno = 0;
    while ((row = mysql_fetch_row(result))) {
      ulong *lengths = mysql_fetch_lengths(result);

      if (row[SHOW_EXTRA]) {
        real_columns[colno] =
            (strcmp(row[SHOW_EXTRA], "STORED GENERATED") != 0 &&
             strcmp(row[SHOW_EXTRA], "STORED GENERATED INVISIBLE") != 0 &&
             strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED") != 0 &&
             strcmp(row[SHOW_EXTRA], "VIRTUAL GENERATED INVISIBLE") != 0);
      } else
        real_columns[colno] = true;

      if (has_invisible_columns && column_list != nullptr) {
        if (!column_list->empty()) column_list->append(", ");
        column_list->append(quote_name(row[SHOW_FIELDNAME], name_buff, false));
      }

      if (!real_columns[colno++]) continue;

      if (init) {
        if (!opt_xml && !opt_no_create_info) {
          fputs(",\n", sql_file);
          check_io(sql_file);
        }
        if (complete_insert) dynstr_append_checked(&insert_pat, ", ");
      }
      init = true;
      if (complete_insert)
        dynstr_append_checked(
            &insert_pat, quote_name(row[SHOW_FIELDNAME], name_buff, false));
      if (!opt_no_create_info) {
        if (opt_xml) {
          print_xml_row(sql_file, "field", result, &row, NullS);
          continue;
        }

        if (opt_keywords)
          fprintf(sql_file, "  %s.%s %s", result_table,
                  quote_name(row[SHOW_FIELDNAME], name_buff, false),
                  row[SHOW_TYPE]);
        else
          fprintf(sql_file, "  %s %s",
                  quote_name(row[SHOW_FIELDNAME], name_buff, false),
                  row[SHOW_TYPE]);
        if (row[SHOW_DEFAULT]) {
          fputs(" DEFAULT ", sql_file);
          unescape(sql_file, row[SHOW_DEFAULT], lengths[SHOW_DEFAULT]);
        }
        if (!row[SHOW_NULL][0]) fputs(" NOT NULL", sql_file);
        if (row[SHOW_EXTRA] && row[SHOW_EXTRA][0])
          fprintf(sql_file, " %s", row[SHOW_EXTRA]);
        check_io(sql_file);
      }
    }
    num_fields = mysql_num_rows(result);
    mysql_free_result(result);
    if (!opt_no_create_info) {
      /* Make an sql-file, if path was given iow. option -T was given */
      char buff[20 + FN_REFLEN];
      uint keynr, primary_key;
      snprintf(buff, sizeof(buff), "show keys from %s", result_table);
      if (mysql_query_with_error_report(mysql, &result, buff)) {
        if (mysql_errno(mysql) == ER_WRONG_OBJECT) {
          /* it is VIEW */
          fputs("\t\t<options Comment=\"view\" />\n", sql_file);
          goto continue_xml;
        }
        fprintf(stderr, "%s: Can't get keys for table %s (%s)\n", my_progname,
                result_table, mysql_error(mysql));
        if (path) my_fclose(sql_file, MYF(MY_WME));
        return 0;
      }

      /* Find first which key is primary key */
      keynr = 0;
      primary_key = INT_MAX;
      while ((row = mysql_fetch_row(result))) {
        if (atoi(row[3]) == 1) {
          keynr++;
          if (!strcmp(row[2], "PRIMARY")) {
            primary_key = keynr;
            break;
          }
        }
      }
      mysql_data_seek(result, 0);
      keynr = 0;
      while ((row = mysql_fetch_row(result))) {
        if (opt_xml) {
          print_xml_row(sql_file, "key", result, &row, NullS);
          continue;
        }

        if (atoi(row[3]) == 1) {
          if (keynr++) putc(')', sql_file);
          if (atoi(row[1])) /* Test if duplicate key */
            /* Duplicate allowed */
            fprintf(sql_file, ",\n  KEY %s (",
                    quote_name(row[2], name_buff, false));
          else if (keynr == primary_key)
            fputs(",\n  PRIMARY KEY (", sql_file); /* First UNIQUE is primary */
          else
            fprintf(sql_file, ",\n  UNIQUE %s (",
                    quote_name(row[2], name_buff, false));
        } else
          putc(',', sql_file);
        fputs(quote_name(row[4], name_buff, false), sql_file);
        if (row[7]) fprintf(sql_file, " (%s)", row[7]); /* Sub key */
        check_io(sql_file);
      }
      mysql_free_result(result);
      if (!opt_xml) {
        if (keynr) putc(')', sql_file);
        fputs("\n)", sql_file);
        check_io(sql_file);
      }

      /* Get MySQL specific create options */
      if (create_options) {
        char show_name_buff[NAME_LEN * 2 + 2 + 24];

        /* Check memory for quote_for_like() */
        snprintf(buff, sizeof(buff), "show table status like %s",
                 quote_for_like(table, show_name_buff));

        if (mysql_query_with_error_report(mysql, &result, buff)) {
          if (mysql_errno(mysql) != ER_PARSE_ERROR) { /* If old MySQL version */
            verbose_msg(
                "-- Warning: Couldn't get status information for "
                "table %s (%s)\n",
                result_table, mysql_error(mysql));
          }
        } else if (!(row = mysql_fetch_row(result))) {
          fprintf(stderr,
                  "Error: Couldn't read status information for table %s (%s)\n",
                  result_table, mysql_error(mysql));
        } else {
          if (opt_xml)
            print_xml_row(sql_file, "options", result, &row, NullS);
          else {
            fputs("/*!", sql_file);
            print_value(sql_file, result, row, "engine=", "Engine", 0);
            print_value(sql_file, result, row, "", "Create_options", 0);
            print_value(sql_file, result, row, "comment=", "Comment", 1);
            fputs(" */", sql_file);
            check_io(sql_file);
          }
        }
        mysql_free_result(result); /* Is always safe to free */
      }
    continue_xml:
      if (!opt_xml)
        fputs(";\n", sql_file);
      else
        fputs("\t</table_structure>\n", sql_file);
      check_io(sql_file);
    }
  }
  if (complete_insert) {
    dynstr_append_checked(&insert_pat, ") VALUES ");
    if (!extended_insert) dynstr_append_checked(&insert_pat, "(");
  }
  if (sql_file != md_result_file) {
    fputs("\n", sql_file);
    write_footer(sql_file);
    my_fclose(sql_file, MYF(MY_WME));
  }
  return (uint)num_fields;
} /* get_table_structure */