ssize_t WbModelImpl::generateReport()

in modules/wb.model/src/reporting.cpp [902:1301]


ssize_t WbModelImpl::generateReport(workbench_physical_ModelRef model, const grt::DictRef &options) {
  // get pointer to the GRT
  std::string basedir = bec::GRTManager::get()->get_basedir();
  std::string template_base_dir = base::makePath(basedir, "modules/data/wb_model_reporting");

  db_mysql_CatalogRef catalog = db_mysql_CatalogRef::cast_from(model->catalog());

  // helper variables
  std::map<std::string, std::vector<db_mysql_ForeignKeyRef> > tbl_fk_map;

  // Process options
  std::string template_name = "HTML Basic Frames";
  std::string template_style_name = "";
  std::string title = "MySQL Model Report";
  std::string output_path = "";
  bool columns_show = true;
  bool indices_show = true;
  bool fks_show = true;
  bool fks_show_referred_fks = true;
  bool show_ddl = true;
  bool use_highlighting = false;
  std::unique_ptr<mtemplate::TemplateOutputFile> single_file_output;

  if (options.is_valid()) {
    read_option(template_name, "template_name", options);
    read_option(template_style_name, "template_style_name", options);
    read_option(title, "title", options);
    read_option(output_path, "output_path", options);
    read_option(columns_show, "columns_show", options);
    read_option(indices_show, "indices_show", options);
    read_option(fks_show, "fks_show", options);
    read_option(fks_show_referred_fks, "fks_show_referred_fks", options);
    read_option(show_ddl, "show_ddl", options);
    read_option(use_highlighting, "use_highlighting", options);
  }

  bool single_file_report = count_template_files(getTemplateDirFromName(template_name)) == 1;

  const Scintilla::LexerModule *lexer = NULL;
  if (use_highlighting)
    lexer = setup_syntax_highlighter(model->rdbms());

  // Ensure the output dir exists.
  {
    int r;

    if (!g_file_test(output_path.c_str(), G_FILE_TEST_EXISTS)) {
      r = g_mkdir_with_parents(output_path.c_str(), 0700);
      if (r < 0) {
        logError("Could not create report directory %s: %s", output_path.c_str(), g_strerror(errno));
        return 0;
      }
    }
  }

  // Start report generation
  grt::GRT::get()->send_info("Generating schema report...");

  // Build FK dictionary if required
  if (fks_show && fks_show_referred_fks) {
    // build schema_dict by loop over all schemata, add it to the main_dict
    for (std::size_t i = 0; i < catalog->schemata().count(); i++) {
      db_mysql_SchemaRef schema = catalog->schemata().get(i);

      // loop over all tables
      for (std::size_t j = 0; j < schema->tables().count(); j++) {
        db_mysql_TableRef table = schema->tables().get(j);

        // loop over all foreign keys
        for (std::size_t k = 0; k < table->foreignKeys().count(); k++) {
          db_mysql_ForeignKeyRef fk = table->foreignKeys().get(k);
          db_mysql_TableRef ref_tbl = fk->referencedTable();

          if (!ref_tbl.is_valid())
            continue;

          // look for table in tbl_fk_map
          std::map<std::string, std::vector<db_mysql_ForeignKeyRef> >::iterator tbl_fk_map_it = tbl_fk_map.find(ref_tbl.id());
          if (tbl_fk_map_it != tbl_fk_map.end()) {
            // if found, add fk reference to table
            tbl_fk_map_it->second.push_back(fk);
          } else {
            // if table is not found, create entry
            std::vector<db_mysql_ForeignKeyRef> new_tbl_fk_map_v;

            // add fk reference to table
            new_tbl_fk_map_v.push_back(fk);

            tbl_fk_map.insert(make_pair(ref_tbl.id(), new_tbl_fk_map_v));
          }
        }
      }
    }
  }

  // create main dictionary that will be used to expand the templates
  mtemplate::DictionaryInterface *main_dictionary = mtemplate::CreateMainDictionary();

  // Set some global project info.
  main_dictionary->setValue(REPORT_TITLE, title);

  std::string time = base::fmttime(0, DATETIME_FMT);
  main_dictionary->setValue(REPORT_GENERATED, time);

  workbench_DocumentRef document = workbench_DocumentRef::cast_from(model->owner());
  main_dictionary->setValue(REPORT_PROJECT_NAME, (std::string)document->info()->project());
  main_dictionary->setValue(REPORT_PROJECT_AUTHOR, (std::string)document->info()->author());
  main_dictionary->setValue(REPORT_PROJECT_TITLE, (std::string)document->info()->caption());
  main_dictionary->setValue(REPORT_PROJECT_CHANGED, (std::string)document->info()->dateChanged());
  main_dictionary->setValue(REPORT_PROJECT_CREATED, (std::string)document->info()->dateCreated());
  main_dictionary->setValue(REPORT_PROJECT_DESCRIPTION, (std::string)document->info()->description());
  main_dictionary->setValue(REPORT_PROJECT_VERSION, (std::string)document->info()->version());

  main_dictionary->dump();

  workbench_model_reporting_TemplateStyleInfoRef styleInfo =
    get_template_style_from_name(template_name, template_style_name);
  if (styleInfo.is_valid())
    main_dictionary->setValue(REPORT_STYLE_NAME, (std::string)styleInfo->styleTagValue());

  main_dictionary->setIntValue(REPORT_SCHEMA_COUNT, (long int)catalog->schemata().count());

  int total_column_count = 0;
  int total_index_count = 0;
  int total_fk_count = 0;
  int total_table_count = 0;
  int total_view_count = 0;
  int total_sp_count = 0;
  int total_trigger_count = 0;

  SQLGeneratorInterfaceImpl *sqlgenModule = NULL;

  if (show_ddl) {
    sqlgenModule = dynamic_cast<SQLGeneratorInterfaceImpl *>(grt::GRT::get()->get_module("DbMySQL"));
    if (!sqlgenModule)
      throw std::logic_error("could not find SQL generation module for mysql");
  }

  // Build schema_dict by looping over all schemata, add it to the main_dict.
  for (int i = 0; i < (int)catalog->schemata().count(); i++) {
    db_mysql_SchemaRef schema = catalog->schemata().get(i);

    mtemplate::DictionaryInterface *schema_dictionary = main_dictionary->addSectionDictionary(REPORT_SCHEMATA);
    schema_dictionary->setIntValue(REPORT_SCHEMA_ID, i);
    schema_dictionary->setIntValue(REPORT_SCHEMA_NUMBER, i + 1);
    schema_dictionary->setValue(REPORT_SCHEMA_NAME, *schema->name());

    set_ddl(schema_dictionary, sqlgenModule, schema, lexer, show_ddl);

    schema_dictionary->setIntValue(REPORT_TABLE_COUNT, (int)schema->tables().count());

    // Loop over all tables. Build the nested tables sub groups and at the same time the
    // full collection of all columns, indices and foreign keys.
    for (int j = 0; j < (int)schema->tables().count(); j++) {
      db_mysql_TableRef table = schema->tables().get(j);

      mtemplate::DictionaryInterface *table_dictionary = schema_dictionary->addSectionDictionary(REPORT_TABLES);

      // The table id is used as unique id, e.g. in HTML anchors.
      table_dictionary->setIntValue(REPORT_TABLE_ID, total_table_count++);

      // The table number is used in visible counts like "Table 1 of 20".
      table_dictionary->setIntValue(REPORT_TABLE_NUMBER, j + 1);

      table_dictionary->setValue(REPORT_TABLE_NAME, *table->name());
      table_dictionary->setValueAndShowSection(REPORT_TABLE_COMMENT, *table->comment(), REPORT_TABLE_COMMENT_LISTING);

      fillTablePropertyDict(table, table_dictionary);
      set_ddl(table_dictionary, sqlgenModule, table, lexer, show_ddl);

      if (columns_show) {
        mtemplate::DictionaryInterface *columns_list_dictionary = NULL;

        schema_dictionary->setIntValue(REPORT_COLUMN_COUNT, (long)table->columns().count());

        for (int k = 0; k < (int)table->columns().count(); k++) {
          // Create the dict for the outer section (including header)
          if (k == 0)
            columns_list_dictionary = table_dictionary->addSectionDictionary(REPORT_COLUMNS_LISTING);

          db_mysql_ColumnRef col = table->columns().get(k);

          // Fill data for table details.
          mtemplate::DictionaryInterface *col_dictionary =
            columns_list_dictionary->addSectionDictionary(REPORT_COLUMNS);

          fillColumnDict(col, table, col_dictionary, false);

          // Fill data for full details.
          col_dictionary = schema_dictionary->addSectionDictionary(REPORT_COLUMNS);

          fillColumnDict(col, table, col_dictionary, true);

          col_dictionary->setIntValue(REPORT_COLUMN_ID, total_column_count++);
          col_dictionary->setIntValue(REPORT_COLUMN_NUMBER, k + 1);
        }
      }

      if (indices_show) {
        mtemplate::DictionaryInterface *idx_list_dictionary = NULL;

        schema_dictionary->setIntValue(REPORT_INDEX_COUNT, (long)table->indices().count());

        for (int k = 0; k < (int)table->indices().count(); k++) {
          // Create the dict for the outer section (including header)
          if (k == 0)
            idx_list_dictionary = table_dictionary->addSectionDictionary(REPORT_INDICES_LISTING);

          db_mysql_IndexRef idx = table->indices().get(k);

          mtemplate::DictionaryInterface *idx_dictionary = idx_list_dictionary->addSectionDictionary(REPORT_INDICES);

          fillIndexDict(idx, table, idx_dictionary, false);

          idx_dictionary = schema_dictionary->addSectionDictionary(REPORT_INDICES);
          fillIndexDict(idx, table, idx_dictionary, true);

          idx_dictionary->setIntValue(REPORT_INDEX_ID, total_index_count++);
          idx_dictionary->setIntValue(REPORT_INDEX_NUMBER, k + 1);
        }
      }

      if (fks_show) {
        mtemplate::DictionaryInterface *fk_list_dictionary = NULL;

        schema_dictionary->setIntValue(REPORT_FOREIGN_KEY_COUNT, (long)table->foreignKeys().count());

        for (int k = 0; k < (int)table->foreignKeys().count(); k++) {
          // Create the dict for the outer section (inluding header)
          if (k == 0)
            fk_list_dictionary = table_dictionary->addSectionDictionary(REPORT_REL_LISTING);

          db_mysql_ForeignKeyRef fk = table->foreignKeys().get(k);

          mtemplate::DictionaryInterface *fk_dictionary = fk_list_dictionary->addSectionDictionary(REPORT_REL);
          fillForeignKeyDict(fk, table, fk_dictionary, false);

          fk_dictionary = schema_dictionary->addSectionDictionary(REPORT_FOREIGN_KEYS);
          fillForeignKeyDict(fk, table, fk_dictionary, true);

          fk_dictionary->setIntValue(REPORT_FOREIGN_KEY_ID, total_fk_count++);
          fk_dictionary->setIntValue(REPORT_FOREIGN_KEY_NUMBER, k + 1);
        }

        if (fks_show_referred_fks) {
          std::map<std::string, std::vector<db_mysql_ForeignKeyRef> >::iterator tbl_fk_map_it = tbl_fk_map.find(table->id());
          if (tbl_fk_map_it != tbl_fk_map.end()) {
            std::vector<db_mysql_ForeignKeyRef>::iterator fk_it = tbl_fk_map_it->second.begin();
            for (; fk_it != tbl_fk_map_it->second.end(); fk_it++) {
              if (fk_list_dictionary == NULL)
                fk_list_dictionary = table_dictionary->addSectionDictionary(REPORT_REL_LISTING);

              db_mysql_ForeignKeyRef fk = *fk_it;

              mtemplate::DictionaryInterface *fk_dictionary = fk_list_dictionary->addSectionDictionary(REPORT_REL);
              fk_dictionary->setValue(REPORT_REL_NAME, *fk->name());
              fk_dictionary->setValue(REPORT_REL_TYPE, bec::TableHelper::is_identifying_foreign_key(table, fk)
                                                         ? "Identifying"
                                                         : "Non-Identifying");
              fk_dictionary->setValue(REPORT_REL_PARENTTABLE, *table->name());
              fk_dictionary->setValue(REPORT_REL_CHILDTABLE, *fk->owner()->name());
              fk_dictionary->setValue(REPORT_REL_CARD, (fk->many() == 1) ? "1:n" : "1:1");
            }
          }
        }

        // Triggers.
        schema_dictionary->setIntValue(REPORT_TRIGGER_COUNT, (long)table->triggers().count());

        for (int k = 0; k < (int)table->triggers().count(); k++) {
          db_mysql_TriggerRef trigger = table->triggers().get(k);

          mtemplate::DictionaryInterface *trigger_dictionary = schema_dictionary->addSectionDictionary(REPORT_TRIGGERS);
          fillTriggerDict(trigger, table, trigger_dictionary);
          set_ddl(trigger_dictionary, sqlgenModule, trigger, lexer, show_ddl);

          trigger_dictionary->setIntValue(REPORT_TRIGGER_ID, total_trigger_count++);
          trigger_dictionary->setIntValue(REPORT_TRIGGER_NUMBER, k + 1);
        }
      }
    }

    // View section.
    schema_dictionary->setIntValue(REPORT_VIEW_COUNT, (long)schema->views().count());
    for (int j = 0; j < (int)schema->views().count(); j++) {
      db_mysql_ViewRef view = schema->views().get(j);

      mtemplate::DictionaryInterface *view_dictionary = schema_dictionary->addSectionDictionary(REPORT_VIEWS);
      view_dictionary->setIntValue(REPORT_VIEW_ID, total_view_count++);
      view_dictionary->setIntValue(REPORT_VIEW_NUMBER, j + 1);
      set_ddl(view_dictionary, sqlgenModule, view, lexer, show_ddl);

      fillViewDict(view, view_dictionary);
    }

    // Routine section.
    schema_dictionary->setIntValue(REPORT_ROUTINE_COUNT, (long)schema->routines().count());
    for (int j = 0; j < (int)schema->routines().count(); j++) {
      db_mysql_RoutineRef routine = schema->routines().get(j);

      mtemplate::DictionaryInterface *routine_dictionary = schema_dictionary->addSectionDictionary(REPORT_ROUTINES);
      routine_dictionary->setIntValue(REPORT_ROUTINE_ID, total_sp_count++);
      routine_dictionary->setIntValue(REPORT_ROUTINE_NUMBER, j + 1);
      set_ddl(routine_dictionary, sqlgenModule, routine, lexer, show_ddl);

      fillRoutineDict(routine, routine_dictionary);
    }
  }

  main_dictionary->setIntValue(REPORT_TOTAL_COLUMN_COUNT, total_column_count);
  main_dictionary->setIntValue(REPORT_TOTAL_INDEX_COUNT, total_index_count);
  main_dictionary->setIntValue(REPORT_TOTAL_FK_COUNT, total_fk_count);
  main_dictionary->setIntValue(REPORT_TOTAL_TABLE_COUNT, total_table_count);
  main_dictionary->setIntValue(REPORT_TOTAL_VIEW_COUNT, total_view_count);
  main_dictionary->setIntValue(REPORT_TOTAL_TRIGGER_COUNT, total_trigger_count);
  main_dictionary->setIntValue(REPORT_TOTAL_ROUTINE_COUNT, total_sp_count);

  // Process template files

  std::string template_dir = getTemplateDirFromName(template_name);

  // loop over all files in the template dir
  const char *entry;
  GDir *dir = g_dir_open(template_dir.c_str(), 0, NULL);
  if (dir) {
    while ((entry = g_dir_read_name(dir)) != NULL) {
      char *path = g_build_filename(template_dir.c_str(), entry, NULL);

      // skip the info.xml file and preview pngs
      if (strcmp(entry, "info.xml") == 0 || (g_str_has_prefix(entry, "preview_") && g_str_has_suffix(entry, ".png"))) {
        g_free(path);
        continue;
      }
      if (g_file_test(path, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
        if (g_str_has_suffix(entry, ".tpl")) {
          // load template file
          mtemplate::Template *template_index = mtemplate::GetTemplate(path, mtemplate::DO_NOT_STRIP);
          if (template_index == 0) {
            grt::GRT::get()->send_error(
              "Error while loading template files. Please check the log for more information.");
            grt::GRT::get()->send_error(path);
            g_free(path);
            return 0;
          }

          // expand the template based on the dictionary
          mtemplate::TemplateOutputString string_output;
          template_index->expand(main_dictionary, &string_output);

          // build output file name
          std::string output_filename;

          if (single_file_report) {
            // For single file reports the target file name is constructed from the report title.
            output_filename = base::makePath(output_path, title);
            std::string template_filename(entry);

            // Remove the .tpl suffix.
            std::string name = template_filename.substr(0, template_filename.size() - 4);

            // Find the file's target suffix. If there is one use this for the target file too.
            std::string::size_type p = name.rfind('.');
            if (p != std::string::npos)
              output_filename += name.substr(p);

            if (single_file_output == nullptr)
              single_file_output =
                std::unique_ptr<mtemplate::TemplateOutputFile>(new mtemplate::TemplateOutputFile(output_filename));

            template_index->expand(main_dictionary, single_file_output.get());
          } else {
            std::string template_filename(entry);
            output_filename = base::makePath(output_path, template_filename.substr(0, template_filename.size() - 4));
            mtemplate::TemplateOutputFile output(output_filename);
            template_index->expand(main_dictionary, &output);
          }
        } else {
          // Copy files/folders.
          std::string target = base::makePath(output_path, entry);
          if (g_file_test(path, G_FILE_TEST_IS_DIR))
            copy_folder(path, target.c_str());
          else
            base::copyFile(path, target.c_str());
        }
      }

      g_free(path);
    }

    g_dir_close(dir);
  }

  if (use_highlighting)
    cleanup_syntax_highlighter();

  grt::GRT::get()->send_info(
    strfmt("Schema report written to %s %s", single_file_report ? "file" : "folder", output_path.c_str()));

  return 1;
}