Skip to content

Commit

Permalink
Include descendants tables that will be deleted in cascade #18 (#20)
Browse files Browse the repository at this point in the history
* Fix targetFilterTableSchemas to include descendants tables

* Add tests to ensure cascade deletable descendants are also targeted.

* Fix some unnatural english sentences.
  • Loading branch information
shuto-facengineer authored Feb 13, 2024
1 parent 99de663 commit 5862e7d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 15 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Application Options:
-i, --instance= (required) Cloud Spanner Instance ID. [$SPANNER_INSTANCE_ID]
-d, --database= (required) Cloud Spanner Database ID. [$SPANNER_DATABASE_ID]
-q, --quiet Disable all interactive prompts.
-t, --tables= Comma separated table names to be truncated. Default to truncate all tables if not specified.
-e, --exclude-tables Comma separated table names to be exempted from truncating. 'tables' and 'exclude-tables' cannot co-exist. If interleaved tables are specified, the parent table is also excluded.
-t, --tables= Comma separated table names to be truncated. Default to truncate all tables if not specified. If an interleaved table is specified, its descendants tables are also truncated.
-e, --exclude-tables Comma separated table names to be exempted from truncating. 'tables' and 'exclude-tables' cannot co-exist. If an interleaved table is specified, its ancestors tables are also excluded.
Help Options:
-h, --help Show this help message
```
Expand Down
17 changes: 14 additions & 3 deletions truncate/table_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func filterTableSchemas(tables []*tableSchema, targetTables, excludeTables []str

// targetFilterTableSchemas filters tables with given targetTables.
// If targetTables is empty, it returns all tables.
// When descendants tables of a target table are cascade deletable, they are also targeted to delete.
func targetFilterTableSchemas(tables []*tableSchema, targetTables []string) []*tableSchema {
if len(targetTables) == 0 {
return tables
Expand All @@ -163,7 +164,17 @@ func targetFilterTableSchemas(tables []*tableSchema, targetTables []string) []*t
isTarget[t] = true
}

// TODO: Add child tables that may be deleted in cascade (#18)
// Additionally include descendants tables that may be deleted in cascade
lineages := constructTableLineages(tables)
for _, l := range lineages {
if isTarget[l.tableSchema.tableName] {
for _, d := range l.descendants {
if d.isCascadeDeletable() {
isTarget[d.tableName] = true
}
}
}
}

filtered := make([]*tableSchema, 0, len(tables))
for _, t := range tables {
Expand All @@ -177,7 +188,7 @@ func targetFilterTableSchemas(tables []*tableSchema, targetTables []string) []*t

// excludeFilterTableSchemas filters tables with given excludeTables.
// If excludeTables is empty, it returns all tables.
// When an exclude table is cascade deletable, its parent table is also excluded.
// When an exclude table is cascade deletable, its ancestors tables are also excluded.
func excludeFilterTableSchemas(tables []*tableSchema, excludeTableSchemas []string) []*tableSchema {
if len(excludeTableSchemas) == 0 {
return tables
Expand All @@ -188,7 +199,7 @@ func excludeFilterTableSchemas(tables []*tableSchema, excludeTableSchemas []stri
isExclude[t] = true
}

// Additionally exclude parent tables that may delete the exclude tables in cascade
// Additionally exclude ancestors tables that may delete the exclude tables in cascade
lineages := constructTableLineages(tables)
for _, l := range lineages {
if isExclude[l.tableSchema.tableName] && l.tableSchema.isCascadeDeletable() {
Expand Down
77 changes: 67 additions & 10 deletions truncate/table_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ import (

func TestTargetFilterTableSchemas(t *testing.T) {
var (
// The following tables are hierarchical schemas and deleted in cascade.
// The table schemas are well known in Cloud Spanner document about 'schema and data model'.
singers = &tableSchema{
tableName: "Singers",
parentTableName: "",
parentOnDeleteAction: deleteActionUndefined,
referencedBy: nil,
}
albums = &tableSchema{
tableName: "Albums",
parentTableName: "Singers",
parentOnDeleteAction: deleteActionCascadeDelete,
referencedBy: nil,
}
songs = &tableSchema{
tableName: "Songs",
parentTableName: "Albums",
parentOnDeleteAction: deleteActionCascadeDelete,
referencedBy: nil,
}

// The following tables are flat schemas and not related to each other.
t1 = &tableSchema{
tableName: "t1",
Expand All @@ -44,6 +65,26 @@ func TestTargetFilterTableSchemas(t *testing.T) {
parentOnDeleteAction: deleteActionUndefined,
referencedBy: nil,
}

// // The following tables are hierarchical schemas and not deleted in cascade.
t4 = &tableSchema{
tableName: "t4",
parentTableName: "",
parentOnDeleteAction: deleteActionUndefined,
referencedBy: nil,
}
t5 = &tableSchema{
tableName: "t5",
parentTableName: "t4",
parentOnDeleteAction: deleteActionNoAction,
referencedBy: nil,
}
t6 = &tableSchema{
tableName: "t6",
parentTableName: "t5",
parentOnDeleteAction: deleteActionNoAction,
referencedBy: nil,
}
)

opts := []cmp.Option{
Expand All @@ -60,19 +101,35 @@ func TestTargetFilterTableSchemas(t *testing.T) {
want []*tableSchema
}{
{
desc: "Include multiple tables",
schemas: []*tableSchema{t1, t2, t3},
targetTables: []string{t1.tableName, t2.tableName},
want: []*tableSchema{t1, t2},
desc: "Include descendants tables by tracing down to the bottommost level.",
schemas: []*tableSchema{singers, albums, songs, t1, t2, t3},
targetTables: []string{singers.tableName},
want: []*tableSchema{singers, albums, songs},
},
{
desc: "Include only the lower levels without the higher levels.",
schemas: []*tableSchema{singers, albums, songs, t1, t2, t3},
targetTables: []string{albums.tableName},
want: []*tableSchema{albums, songs},
},
{
desc: "Include multiple tables.",
schemas: []*tableSchema{singers, albums, songs, t1, t2, t3},
targetTables: []string{singers.tableName, t1.tableName, t2.tableName},
want: []*tableSchema{singers, albums, songs, t1, t2},
},
{
desc: "Do nothing when no target tables are passed.",
schemas: []*tableSchema{t1, t2, t3},
schemas: []*tableSchema{singers, albums, songs, t1, t2, t3},
targetTables: nil,
want: []*tableSchema{t1, t2, t3},
want: []*tableSchema{singers, albums, songs, t1, t2, t3},
},
{
desc: "Do not include descendants tables that will not be deleted in cascade.",
schemas: []*tableSchema{singers, albums, songs, t4, t5, t6},
targetTables: []string{singers.tableName, t4.tableName},
want: []*tableSchema{singers, albums, songs, t4},
},
// TODO: Determine the specifications for parent-child relationships in hierarchical interleaved tables, and add corresponding tests and implementation.
// This includes defining the behavior of targetFilterTableSchemas for cases where tables not included in the target list are subject to cascade deletion.
} {
t.Run(test.desc, func(t *testing.T) {
got := targetFilterTableSchemas(test.schemas, test.targetTables)
Expand Down Expand Up @@ -166,7 +223,7 @@ func TestExcludeFilterTableSchemas(t *testing.T) {
want []*tableSchema
}{
{
desc: "Exclude the parent tables by tracing up to the topmost level.",
desc: "Exclude ancestors tables by tracing up to the topmost level.",
schemas: []*tableSchema{singers, albums, songs, t1, t2, t3},
excludeTables: []string{songs.tableName},
want: []*tableSchema{t1, t2, t3},
Expand All @@ -190,7 +247,7 @@ func TestExcludeFilterTableSchemas(t *testing.T) {
want: []*tableSchema{singers, albums, songs, t1, t2, t3},
},
{
desc: "Do not exclude the parent tables that are not deleted in cascade.",
desc: "Do not exclude ancestors tables that are not deleted in cascade.",
schemas: []*tableSchema{singers, albums, songs, t4, t5, t6},
excludeTables: []string{songs.tableName, t6.tableName},
want: []*tableSchema{t4, t5},
Expand Down

0 comments on commit 5862e7d

Please sign in to comment.