Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Order views by dependency order in SCRIPT output (#2391) #3022

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 61 additions & 6 deletions h2/src/main/org/h2/command/dml/ScriptCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.constraint.Constraint;
Expand Down Expand Up @@ -57,6 +61,7 @@
import org.h2.table.PlanItem;
import org.h2.table.Table;
import org.h2.table.TableType;
import org.h2.table.TableView;
import org.h2.util.HasSQL;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
Expand Down Expand Up @@ -231,13 +236,10 @@ public ResultInterface query(long maxrows) {
}
}

final ArrayList<Table> tables = db.getAllTablesAndViews();
// sort by id, so that views are after tables and views on views
// after the base views
tables.sort(Comparator.comparingInt(Table::getId));
final ArrayList<Table> sortedTablesAndViews = getSortedTablesAndViews(db);

// Generate the DROP XXX ... IF EXISTS
for (Table table : tables) {
for (Table table : sortedTablesAndViews) {
if (excludeSchema(table.getSchema())) {
continue;
}
Expand Down Expand Up @@ -280,7 +282,7 @@ public ResultInterface query(long maxrows) {

// Generate CREATE TABLE and INSERT...VALUES
int count = 0;
for (Table table : tables) {
for (Table table : sortedTablesAndViews) {
if (excludeSchema(table.getSchema())) {
continue;
}
Expand Down Expand Up @@ -382,6 +384,59 @@ public ResultInterface query(long maxrows) {
return r;
}

// First tables ordered by ID, then views in dependency order
private ArrayList<Table> getSortedTablesAndViews(Database db) {
final ArrayList<Table> allTablesAndViews = db.getAllTablesAndViews();
allTablesAndViews.sort(Comparator.comparingInt(Table::getId));
final ArrayList<TableView> allViews = new ArrayList<>();
final ArrayList<Table> sortedTablesAndViews = new ArrayList<>();
for (Table table : allTablesAndViews) {
if (table instanceof TableView) {
allViews.add((TableView) table);
} else {
sortedTablesAndViews.add(table);
}
}
final List<TableView> sortedViews = new TableViewSorter(allViews).sortByDependencies();
sortedTablesAndViews.addAll(sortedViews);
return sortedTablesAndViews;
}

// Topological sort of list of TableViews
public static class TableViewSorter {
private final List<TableView> views;
private final List<TableView> order;
private final Map<TableView, Boolean> visited;

public TableViewSorter(List<TableView> views) {
this.views = views;
this.order = new ArrayList<>(views.size());
this.visited = new HashMap<>(views.size());
}

public List<TableView> sortByDependencies() {
for (TableView view : views) {
visited.put(view, false);
}
for (TableView view : views) {
if (Boolean.FALSE.equals(visited.get(view))) {
sortHelper(view);
}
}
return order;
}

private void sortHelper(TableView v) {
visited.replace(v, true);
for (Table table : v.getTables()) {
if (table instanceof TableView && Boolean.FALSE.equals(visited.get(table))) {
sortHelper((TableView) table);
}
}
order.add(v);
}
}

private void dumpDomains(ArrayList<Schema> schemas) throws IOException {
TreeMap<Domain, TreeSet<Domain>> referencingDomains = new TreeMap<>(BY_NAME_COMPARATOR);
TreeSet<Domain> known = new TreeSet<>(BY_NAME_COMPARATOR);
Expand Down
45 changes: 45 additions & 0 deletions h2/src/test/org/h2/test/scripts/testScript.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7103,3 +7103,48 @@ select * from test where "YEAR" in (select distinct "YEAR" from test order by "Y

drop table test;
> ok

----- Issue#2390 -----
CREATE TABLE TEST_TABLE ("ID" NUMBER(19, 0), "TEXT" VARCHAR(100));
Comment on lines +7106 to +7108
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Please, don't add new tests to testScript.sql and testSimple.sql, use some files relevant to the command that you want to fix.
  2. Use explicit CREATE MEMORY TABLE in tests of SCRIPT command to avoid failures during tests of persistent databases.
  3. NUMBER is not a native data type of H2; it would be better to use NUMERIC(19) or any other standard or H2's own data type.

> ok

CREATE VIEW TEST_VIEW("ID", "TEXT") AS SELECT ID, TEXT FROM TEST_TABLE;
> ok

ALTER TABLE TEST_TABLE ADD COLUMN ("NEW_COLUMN" VARCHAR(10));
> ok

script nodata nopasswords nosettings noversion;
> SCRIPT
> ------------------------------------------------------------------------------------------------------------------------------------
> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN;
> CREATE MEMORY TABLE "PUBLIC"."TEST_TABLE"( "ID" NUMERIC(19, 0), "TEXT" CHARACTER VARYING(100), "NEW_COLUMN" CHARACTER VARYING(10) );
> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST_TABLE;
> CREATE FORCE VIEW "PUBLIC"."TEST_VIEW"("ID", "TEXT") AS SELECT "ID", "TEXT" FROM "PUBLIC"."TEST_TABLE";
> rows (ordered): 4

----- Issue#2391 -----
CREATE VIEW TEST_VIEW_INNER("ID", "TEXT") AS SELECT ID, TEXT FROM TEST_TABLE;
> ok

CREATE OR REPLACE FORCE VIEW TEST_VIEW("ID", "TEXT") AS SELECT ID, TEXT FROM TEST_VIEW_INNER;
> ok

script nodata nopasswords nosettings noversion;
> SCRIPT
> ------------------------------------------------------------------------------------------------------------------------------------
> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN;
> CREATE MEMORY TABLE "PUBLIC"."TEST_TABLE"( "ID" NUMERIC(19, 0), "TEXT" CHARACTER VARYING(100), "NEW_COLUMN" CHARACTER VARYING(10) );
> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST_TABLE;
> CREATE FORCE VIEW "PUBLIC"."TEST_VIEW_INNER"("ID", "TEXT") AS SELECT "ID", "TEXT" FROM "PUBLIC"."TEST_TABLE";
> CREATE FORCE VIEW "PUBLIC"."TEST_VIEW"("ID", "TEXT") AS SELECT "ID", "TEXT" FROM "PUBLIC"."TEST_VIEW_INNER";
> rows (ordered): 5

DROP VIEW "PUBLIC"."TEST_VIEW";
> ok

DROP VIEW "PUBLIC"."TEST_VIEW_INNER";
> ok

DROP TABLE "PUBLIC"."TEST_TABLE";
> ok