You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SQSchemaDumper.java 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.db.dump;
  21. import java.sql.Connection;
  22. import java.sql.ResultSet;
  23. import java.sql.SQLException;
  24. import java.sql.Statement;
  25. import java.util.ArrayList;
  26. import java.util.Comparator;
  27. import java.util.List;
  28. import org.sonar.db.SQDatabase;
  29. import static com.google.common.base.Preconditions.checkState;
  30. class SQSchemaDumper {
  31. private static final String LINE_SEPARATOR = System.lineSeparator();
  32. private static final String HEADER = "" +
  33. "###############################################################" + LINE_SEPARATOR +
  34. "#### Description of SonarQube's schema in H2 SQL syntax ####" + LINE_SEPARATOR +
  35. "#### ####" + LINE_SEPARATOR +
  36. "#### This file is autogenerated and stored in SCM to ####" + LINE_SEPARATOR +
  37. "#### conveniently read the SonarQube's schema at any ####" + LINE_SEPARATOR +
  38. "#### point in time. ####" + LINE_SEPARATOR +
  39. "#### ####" + LINE_SEPARATOR +
  40. "#### DO NOT MODIFY THIS FILE DIRECTLY ####" + LINE_SEPARATOR +
  41. "#### use gradle task :server:sonar-db-dao:dumpSchema ####" + LINE_SEPARATOR +
  42. "###############################################################" + LINE_SEPARATOR;
  43. private static final String TABLE_SCHEMA_MIGRATIONS = "SCHEMA_MIGRATIONS";
  44. private static final Comparator<String> SCHEMA_MIGRATIONS_THEN_NATURAL_ORDER = ((Comparator<String>) (o1, o2) -> {
  45. if (o1.equals(TABLE_SCHEMA_MIGRATIONS)) {
  46. return -1;
  47. }
  48. if (o2.equals(TABLE_SCHEMA_MIGRATIONS)) {
  49. return 1;
  50. }
  51. return 0;
  52. }).thenComparing(String.CASE_INSENSITIVE_ORDER);
  53. String dumpToText() throws SQLException {
  54. SQDatabase database = SQDatabase.newH2Database("SQSchemaDumper", true);
  55. database.start();
  56. try (Connection connection = database.getDataSource().getConnection();
  57. Statement statement = connection.createStatement()) {
  58. List<String> tableNames = getSortedTableNames(statement);
  59. StringBuilder res = new StringBuilder(HEADER);
  60. for (String tableName : tableNames) {
  61. res.append(LINE_SEPARATOR);
  62. dumpTable(statement, res, tableName);
  63. }
  64. return res.toString();
  65. }
  66. }
  67. /**
  68. * List of all tables in database sorted in natural order except that table {@link #TABLE_SCHEMA_MIGRATIONS SCHEMA_MIGRATIONS}
  69. * will always be first.
  70. */
  71. private List<String> getSortedTableNames(Statement statement) throws SQLException {
  72. checkState(statement.execute("SHOW TABLES"), "can't list tables");
  73. List<String> tableNames = new ArrayList<>();
  74. try (ResultSet rSet = statement.getResultSet()) {
  75. while (rSet.next()) {
  76. tableNames.add(rSet.getString(1));
  77. }
  78. }
  79. tableNames.sort(SCHEMA_MIGRATIONS_THEN_NATURAL_ORDER);
  80. return tableNames;
  81. }
  82. private void dumpTable(Statement statement, StringBuilder res, String tableName) throws SQLException {
  83. checkState(statement.execute("SCRIPT NODATA NOSETTINGS TABLE " + tableName), "Can't get schema dump of table %s", tableName);
  84. try (ResultSet resultSet = statement.getResultSet()) {
  85. while (resultSet.next()) {
  86. String sql = resultSet.getString(1);
  87. if (isIgnoredStatement(sql)) {
  88. continue;
  89. }
  90. String cleanedSql = sql
  91. .replaceAll(" PUBLIC\\.", " ")
  92. .replaceAll(" MEMORY TABLE ", " TABLE ");
  93. if (cleanedSql.startsWith("CREATE TABLE")) {
  94. cleanedSql = fixAutoIncrementIdColumn(cleanedSql);
  95. }
  96. res.append(cleanedSql).append(LINE_SEPARATOR);
  97. }
  98. }
  99. }
  100. private static boolean isIgnoredStatement(String sql) {
  101. return sql.startsWith("CREATE SEQUENCE") || sql.startsWith("CREATE USER") || sql.startsWith("--");
  102. }
  103. /**
  104. * Hacky hacky hack: H2 generates DDL for auto increment column which varies from one run to another and is hardly
  105. * readable for user.
  106. * It's currently reasonable to assume:
  107. * <ul>
  108. * <li>all existing auto increment columns are called ID</li>
  109. * <li>it's not a practice to create auto increment anymore => no new will be added which could have a different name</li>
  110. * </ul>
  111. */
  112. private String fixAutoIncrementIdColumn(String cleanedSql) {
  113. String res = fixAutoIncrementIdColumn(cleanedSql, "ID INTEGER DEFAULT (NEXT VALUE FOR ", "ID INTEGER NOT NULL AUTO_INCREMENT (1,1)");
  114. res = fixAutoIncrementIdColumn(res, "ID BIGINT DEFAULT (NEXT VALUE FOR ", "ID BIGINT NOT NULL AUTO_INCREMENT (1,1)");
  115. return res;
  116. }
  117. private static String fixAutoIncrementIdColumn(String sql, String src, String tgt) {
  118. int idAutoGenColumn = sql.indexOf(src);
  119. if (idAutoGenColumn < 0) {
  120. return sql;
  121. }
  122. int comma = sql.indexOf(",", idAutoGenColumn + 1);
  123. checkState(comma > -1, "can't find end of ID column declaration??");
  124. StringBuilder bar = new StringBuilder(sql);
  125. bar.replace(idAutoGenColumn, comma, tgt);
  126. return bar.toString();
  127. }
  128. }