diff --git a/tests/test_doc_mentions_go.c b/tests/test_doc_mentions_go.c index 78f1934e0..0d4462f76 100644 --- a/tests/test_doc_mentions_go.c +++ b/tests/test_doc_mentions_go.c @@ -228,10 +228,10 @@ TEST(doc_mentions_go_sources) { const char *raw; const char *source; } want[] = { - {"RefF", "p.pkg.F"}, {"RefS", "p.pkg.S"}, {"RefField", "p.pkg.S.Field"}, - {"RefM", "p.pkg.M"}, /* Go method nodes have a flat name */ - {"RefI", "p.pkg.I"}, {"RefDo", "p.pkg.I.Do"}, {"RefC", "p.pkg.C"}, - {"RefK", "p.pkg.K"}, {"RefV", "p.pkg.V"}, {"RefA", "p.pkg.A"}, + {"RefF", "p.pkg.F"}, {"RefS", "p.pkg.S"}, {"RefField", "p.pkg.S.Field"}, + {"RefM", "p.pkg.S.M"}, /* #1913: Go method nodes are receiver-qualified */ + {"RefI", "p.pkg.I"}, {"RefDo", "p.pkg.I.Do"}, {"RefC", "p.pkg.C"}, + {"RefK", "p.pkg.K"}, {"RefV", "p.pkg.V"}, {"RefA", "p.pkg.A"}, }; for (size_t i = 0; i < GO_COUNT(want); i++) { const CBMDocLink *t = dm_find_token(r, want[i].raw); @@ -772,14 +772,15 @@ TEST(doc_mentions_go_scope_blob) { "T\tAlias\ta\tal.Widget\t1", "T\tSame\ta\tKind\t1", "T\tList\ts\t-\t1", - /* functions and methods; two methods named Run compete for one node - * and the later one keeps it */ + /* functions and methods; a method node is receiver-qualified + * (#1913), so a method claims no flat name and its w is `-`: the two + * methods named Run no longer compete */ "F\tNew\t1", "F\tMap\t1", - "M\tWidget\tRun\t0", - "M\tKind\tRun\t1", - "M\tList\tPush\t1", - "M\tlower\tExported\t1", + "M\tWidget\tRun\t-", + "M\tKind\tRun\t-", + "M\tList\tPush\t-", + "M\tlower\tExported\t-", /* own type parameters, by the declaration's first and last line */ "g\t36\t36\tT,U", "g\t42\t42\tA,B", @@ -814,15 +815,20 @@ TEST(doc_mentions_go_scope_blob) { char *portable = cbm_doclink_portable_scope(s); ASSERT_NOT_NULL(portable); ASSERT_NULL(strstr(portable, "\ng\t")); - ASSERT_TRUE(go_has_rec(portable, "M\tKind\tRun\t1")); + ASSERT_TRUE(go_has_rec(portable, "M\tKind\tRun\t-")); ASSERT(strncmp(portable, "go1\n", 4) == 0); cbm_free(CBM_MEM_CLASS_OTHER, portable); cbm_free_result(r); - /* moving declarations changes nothing unless a node changes its owner */ + /* moving declarations changes nothing unless a node changes its owner. + * Methods own no flat name (#1913), so swapping two methods of one name + * changes nothing either; two package-level declarations of one name + * still hand . over when swapped */ const char *a = "package p\n\nfunc (a A) Run() {}\nfunc (b B) Run() {}\nfunc F() {}\n"; const char *moved = "package p\n\nfunc F() {}\n\n\nfunc (a A) Run() {}\nfunc (b B) Run() {}\n"; const char *swapped = "package p\n\nfunc (b B) Run() {}\nfunc (a A) Run() {}\nfunc F() {}\n"; + const char *pair = "package p\n\nfunc Open() {}\ntype Open struct{}\n"; + const char *pair_swapped = "package p\n\ntype Open struct{}\nfunc Open() {}\n"; CBMFileResult *ra = go_extract(a, "p/x.go"); CBMFileResult *rm = go_extract(moved, "p/x.go"); CBMFileResult *rs = go_extract(swapped, "p/x.go"); @@ -830,16 +836,25 @@ TEST(doc_mentions_go_scope_blob) { ASSERT_NOT_NULL(rm); ASSERT_NOT_NULL(rs); ASSERT_STR_EQ(ra->doc_scope, rm->doc_scope); - ASSERT(strcmp(ra->doc_scope, rs->doc_scope) != 0); - ASSERT_TRUE(go_has_rec(rs->doc_scope, "M\tA\tRun\t1")); - ASSERT_TRUE(go_has_rec(rs->doc_scope, "M\tB\tRun\t0")); + ASSERT_STR_EQ(ra->doc_scope, rs->doc_scope); + ASSERT_TRUE(go_has_rec(rs->doc_scope, "M\tA\tRun\t-")); + ASSERT_TRUE(go_has_rec(rs->doc_scope, "M\tB\tRun\t-")); cbm_free_result(ra); cbm_free_result(rm); cbm_free_result(rs); + CBMFileResult *rp = go_extract(pair, "p/x.go"); + CBMFileResult *rq = go_extract(pair_swapped, "p/x.go"); + ASSERT_NOT_NULL(rp); + ASSERT_NOT_NULL(rq); + ASSERT(strcmp(rp->doc_scope, rq->doc_scope) != 0); + ASSERT_TRUE(go_has_rec(rp->doc_scope, "T\tOpen\ts\t-\t1")); + ASSERT_TRUE(go_has_rec(rq->doc_scope, "F\tOpen\t1")); + cbm_free_result(rp); + cbm_free_result(rq); - /* the node . is one for a function, a type, a value and the - * methods of that name: the last declaration keeps it, whatever its kind, - * and two on one line contest it */ + /* the node . is one for a function, a type and a value of + * that name: the last declaration keeps it, whatever its kind, and two on + * one line contest it. A method of that name claims nothing (#1913) */ CBMFileResult *k1 = go_extract("package p\n\nfunc Open() {}\nfunc (t T) Open() {}\n", "p/k.go"); CBMFileResult *k2 = go_extract("package p\n\nfunc (t T) Open() {}\nfunc Open() {}\n", "p/k.go"); CBMFileResult *k3 = go_extract("package p\n\ntype Open int\nvar Open = 1\n" @@ -848,14 +863,21 @@ TEST(doc_mentions_go_scope_blob) { ASSERT_NOT_NULL(k1); ASSERT_NOT_NULL(k2); ASSERT_NOT_NULL(k3); - ASSERT_TRUE(go_has_rec(k1->doc_scope, "F\tOpen\t0")); - ASSERT_TRUE(go_has_rec(k1->doc_scope, "M\tT\tOpen\t1")); + ASSERT_TRUE(go_has_rec(k1->doc_scope, "F\tOpen\t1")); + ASSERT_TRUE(go_has_rec(k1->doc_scope, "M\tT\tOpen\t-")); ASSERT_TRUE(go_has_rec(k2->doc_scope, "F\tOpen\t1")); - ASSERT_TRUE(go_has_rec(k2->doc_scope, "M\tT\tOpen\t0")); + ASSERT_TRUE(go_has_rec(k2->doc_scope, "M\tT\tOpen\t-")); ASSERT_TRUE(go_has_rec(k3->doc_scope, "T\tOpen\to\t-\t0")); ASSERT_TRUE(go_has_rec(k3->doc_scope, "V\tOpen\t0")); - ASSERT_TRUE(go_has_rec(k3->doc_scope, "V\tOpen\tx")); - ASSERT_TRUE(go_has_rec(k3->doc_scope, "M\tT\tOpen\tx")); + ASSERT_TRUE(go_has_rec(k3->doc_scope, "V\tOpen\t1")); + ASSERT_TRUE(go_has_rec(k3->doc_scope, "M\tT\tOpen\t-")); + /* two package-level declarations on one line contest the name */ + CBMFileResult *k4 = + go_extract("package p\n\ntype Open int\nvar Open = 1; const Open = 2\n", "p/k.go"); + ASSERT_NOT_NULL(k4); + ASSERT_TRUE(go_has_rec(k4->doc_scope, "T\tOpen\to\t-\t0")); + ASSERT_TRUE(go_has_rec(k4->doc_scope, "V\tOpen\tx")); + cbm_free_result(k4); cbm_free_result(k1); cbm_free_result(k2); cbm_free_result(k3); @@ -1030,7 +1052,7 @@ static const char GO_ALPHA_C[] = "package alpha\n" "// Target is a target; see [A.Close] and [B.Close].\n" "func Target() {}\n" "\n" - "// Open is a type that loses its node name to the method below.\n" + "// Open is a type; a method of B below has the same name.\n" "type Open struct{}\n" "\n" "// Open opens: a method named like the type.\n" @@ -1295,7 +1317,7 @@ TEST(doc_mentions_go_forms) { static const go_want_edge_t edges[] = { /* [Name], [Recv.Method], an interface's method, a constant, a variable */ {"alpha.Widget", "alpha.New", "unique"}, - {"alpha.Widget", "alpha.Run", "unique"}, + {"alpha.Widget", "alpha.Widget.Run", "unique"}, {"alpha.Widget", "alpha.Runner.Run", "unique"}, {"alpha.Widget", "alpha.Max", "unique"}, {"alpha.Widget", "alpha.Default", "unique"}, @@ -1311,7 +1333,7 @@ TEST(doc_mentions_go_forms) { {"alpha.New", "sub.deep.Deep", "exact"}, {"alpha.New", "nested.pkg.Far", "exact"}, /* [pkg.Recv.Name]: a method, a field, an interface method */ - {"alpha.New", "beta.Do", "exact"}, + {"alpha.New", "beta.Thing.Do", "exact"}, {"alpha.New", "beta.Thing.Field", "exact"}, {"alpha.New", "beta.Doer.Act", "exact"}, /* an identifier with letters beyond ASCII */ @@ -1395,7 +1417,7 @@ TEST(doc_mentions_go_plain_text) { ASSERT_EQ(go_bad_rows(db, rows, GO_COUNT(rows)), 0); /* and none of them is an edge: Run's doc holds rows, plain text and one * link, to the field Widget.Size */ - ASSERT_EQ(dm_mentions_from(db, "alpha.Run"), 1); + ASSERT_EQ(dm_mentions_from(db, "alpha.Widget.Run"), 1); ASSERT_EQ(dm_mentions_from(db, "alpha.Bare"), 0); static const go_want_edge_t edges[] = { {"alpha.Sibling", "alpha.Widget", "unique"}, @@ -1424,14 +1446,14 @@ TEST(doc_mentions_go_struct_fields) { static const go_want_edge_t edges[] = { /* the same package: a field named from a method's doc and from a * sibling field's doc */ - {"alpha.Run", "alpha.Widget.Size", "exact"}, + {"alpha.Widget.Run", "alpha.Widget.Size", "exact"}, {"alpha.Widget.Name", "alpha.Widget.Size", "exact"}, /* another package, by its import */ {"alpha.New", "beta.Thing.Field", "exact"}, {"gamma.ViaAlias", "alpha.Widget.Name", "exact"}, /* Depth is a field of beta.Embedded, which Widget embeds: it is no * field of Widget */ - {"alpha.Run", "beta.Embedded.Depth", ""}, + {"alpha.Widget.Run", "beta.Embedded.Depth", ""}, }; ASSERT_EQ(go_bad_edges(db, edges, GO_COUNT(edges)), 0); /* the promoted field exists as a node: the test above is not vacuous */ @@ -1545,7 +1567,7 @@ TEST(doc_mentions_go_lookup_order) { * file that imports "fmt", the repository's own in a file that imports * example.com/demo/fmt */ ASSERT_STR_EQ(go_reason(db, "alpha/a.go", "fmt.Println"), "external"); - ASSERT_STR_EQ(go_tier(db, "alpha.Run", "fmt.Println"), ""); + ASSERT_STR_EQ(go_tier(db, "alpha.Widget.Run", "fmt.Println"), ""); ASSERT_STR_EQ(go_tier(db, "gamma.Local", "fmt.Println"), "exact"); /* 5. the longest module path decides: a directory inside the root module * that belongs to a nested module of another path is not reachable through @@ -1555,17 +1577,17 @@ TEST(doc_mentions_go_lookup_order) { ASSERT_STR_EQ(go_tier(db, "alpha.New", "sub.deep.Deep"), "exact"); /* 6. the bare name stops at the package: beta.Make is imported and still * no [Make] */ - ASSERT_STR_EQ(go_tier(db, "alpha.Run", "beta.Make"), ""); + ASSERT_STR_EQ(go_tier(db, "alpha.Widget.Run", "beta.Make"), ""); /* 7. members stop at the type: nothing promoted through embedding */ ASSERT_STR_EQ(go_reason(db, "gamma/k.go", "alpha.Widget.Promoted"), "missing"); - ASSERT_STR_EQ(go_tier(db, "gamma.ViaAlias", "beta.Promoted"), ""); + ASSERT_STR_EQ(go_tier(db, "gamma.ViaAlias", "beta.Embedded.Promoted"), ""); /* 8. an alias is followed for a member written with its package, to the * type that declares the member */ static const go_want_edge_t alias[] = { - {"gamma.ViaAlias", "beta.Do", "exact"}, + {"gamma.ViaAlias", "beta.Thing.Do", "exact"}, {"gamma.ViaAlias", "beta.Thing.Field", "exact"}, {"gamma.ViaAlias", "alpha.Alias", "exact"}, - {"gamma.ViaAlias", "alpha.End", "exact"}, /* seven aliases away */ + {"gamma.ViaAlias", "alpha.Deep10.End", "exact"}, /* seven aliases away */ {"gamma.ViaAlias", "alpha.Widget.Name", "exact"}, }; ASSERT_EQ(go_bad_edges(db, alias, GO_COUNT(alias)), 0); @@ -1638,28 +1660,42 @@ TEST(doc_mentions_go_node_owner) { char tmp[256]; char db[512]; ASSERT_EQ(go_index_lookup(tmp, sizeof(tmp), db, sizeof(db)), 0); - /* Go method nodes have a flat name: A.Close and B.Close share - * alpha.Close, and the later declaration keeps it */ - ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM nodes WHERE qualified_name LIKE '%.alpha.Close'"), + /* #1913: Go method nodes are receiver-qualified. A.Close and B.Close are + * two nodes (no shared alpha.Close), each a target of its own */ + ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM nodes WHERE label = 'Method' AND " + "qualified_name LIKE '%.alpha.A.Close'"), 1); - ASSERT_STR_EQ(go_tier(db, "alpha.Target", "alpha.Close"), "unique"); - ASSERT_STR_EQ(go_reason(db, "alpha/c.go", "A.Close"), "graph_gap"); + ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM nodes WHERE label = 'Method' AND " + "qualified_name LIKE '%.alpha.B.Close'"), + 1); + ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM nodes WHERE qualified_name LIKE '%.alpha.Close'"), + 0); + ASSERT_STR_EQ(go_tier(db, "alpha.Target", "alpha.A.Close"), "unique"); + ASSERT_STR_EQ(go_tier(db, "alpha.Target", "alpha.B.Close"), "unique"); + ASSERT_STR_EQ(go_reason(db, "alpha/c.go", "A.Close"), ""); ASSERT_STR_EQ(go_reason(db, "alpha/c.go", "B.Close"), ""); - /* the doc of the method that lost the name is not attributed to the one - * that kept it: B.Close's [Target] is the edge, A.Close's a row */ + /* each method's doc is attributed to its own node: both [Target]s are + * edges, from the line of that method's doc, and neither is a row */ char props[512]; int n = 0; - dm_edge(db, "alpha.Close", "alpha.Target", props, sizeof(props), &n); + dm_edge(db, "alpha.A.Close", "alpha.Target", props, sizeof(props), &n); + ASSERT_EQ(n, 1); + ASSERT_NOT_NULL(strstr(props, "\"line\":9,\"count\":1")); + dm_edge(db, "alpha.B.Close", "alpha.Target", props, sizeof(props), &n); ASSERT_EQ(n, 1); ASSERT_NOT_NULL(strstr(props, "\"line\":12,\"count\":1")); - ASSERT_EQ(dm_count(db, "SELECT line FROM doc_link_unresolved WHERE rel_path = 'alpha/c.go' " - "AND raw = 'Target'"), - 9); - /* a type that lost its name to a method of that name is no target, the - * method is */ - ASSERT_STR_EQ(go_reason(db, "alpha/c.go", "Open"), "graph_gap"); + ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM doc_link_unresolved WHERE rel_path = " + "'alpha/c.go' AND raw = 'Target'"), + 0); + /* a type and a method of the same name no longer share a node: [Open] + * is the type, [B.Open] the method */ + ASSERT_STR_EQ(go_reason(db, "alpha/c.go", "Open"), ""); + ASSERT_EQ(dm_count(db, "SELECT COUNT(*) FROM nodes WHERE label <> 'Method' AND " + "qualified_name LIKE '%.alpha.Open'"), + 1); ASSERT_STR_EQ(go_tier(db, "alpha.UsesOpen", "alpha.Open"), "unique"); - ASSERT_EQ(dm_mentions_from(db, "alpha.UsesOpen"), 1); + ASSERT_STR_EQ(go_tier(db, "alpha.UsesOpen", "alpha.B.Open"), "unique"); + ASSERT_EQ(dm_mentions_from(db, "alpha.UsesOpen"), 2); /* build variants are one entity: whichever file holds the node */ static const go_want_edge_t variants[] = { @@ -1836,15 +1872,16 @@ TEST(doc_mentions_go_incremental_equals_full) { ASSERT_EQ(dm_index(repo, inc_db, NULL), 0); /* the preconditions the steps below move away from */ static const go_want_edge_t before[] = { - {"a.Use", "b.Target", "exact"}, {"a.Use", "b.Gone", "exact"}, - {"a.Use", "b.M", "exact"}, /* T.M, in the first file of the directory, keeps b.M */ - {"a.Use", "b.Name", "exact"}, /* W.Name, the later declaration, keeps b.Name */ - {"a.Use", "a.Local", "unique"}, {"a.doc.go.__file__", "a.Local", "unique"}, + {"a.Use", "b.Target", "exact"}, {"a.Use", "b.Gone", "exact"}, + {"a.Use", "b.T.M", "exact"}, /* #1913: every method has its own node */ + {"a.Use", "b.W.Name", "exact"}, {"a.Use", "b.V.Name", "exact"}, + {"b.U.M", "b.Target", "unique"}, /* U.M is a source of its own */ + {"a.Use", "a.Local", "unique"}, {"a.doc.go.__file__", "a.Local", "unique"}, {"a.Other", "b.Target", ""}, /* other.go does not import b */ }; ASSERT_EQ(go_bad_edges(inc_db, before, GO_COUNT(before)), 0); - ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.V.Name"), "graph_gap"); - ASSERT_STR_EQ(go_reason(inc_db, "b/c.go", "Target"), "graph_gap"); /* U.M has no node */ + ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.V.Name"), ""); + ASSERT_STR_EQ(go_reason(inc_db, "b/c.go", "Target"), ""); /* a doc-comment edit re-extracts the file alone */ go_w(repo, "a/a.go", GO_INC_A_DOC_EDIT); @@ -1880,23 +1917,27 @@ TEST(doc_mentions_go_incremental_equals_full) { 0); ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.Gone"), "missing"); - /* the method that kept a shared node name goes: the other file's method of - * that name takes the node over, and its doc becomes a source */ + /* a method goes: links to it break, and another receiver's method of + * the same name, in another file, is untouched */ go_w(repo, "b/b.go", GO_INC_B_NO_M); ASSERT_EQ( dm_step(repo, inc_db, full_db, "name keeper removed", CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR), 0); ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.T.M"), "missing"); - ASSERT_STR_EQ(go_tier(inc_db, "b.M", "b.Target"), "unique"); + ASSERT_STR_EQ(go_tier(inc_db, "b.U.M", "b.Target"), "unique"); ASSERT_STR_EQ(go_reason(inc_db, "b/c.go", "Target"), ""); - /* declaration order decides which declaration keeps a shared name: - * swapping two methods moves the node, for files with no edge into it too */ + /* declaration order no longer decides anything: swapping two methods of + * the same name keeps both nodes and both links */ go_w(repo, "b/d.go", GO_INC_D_SWAPPED); - ASSERT_EQ(dm_step(repo, inc_db, full_db, "methods swapped", CBM_INCREMENTAL_ROUTE_FORCED_FULL), - 0); - ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.W.Name"), "graph_gap"); + /* and the scope blob does not change (no method w moves), so the step + * is an ordinary closure repair, no longer a forced full rebuild */ + ASSERT_EQ( + dm_step(repo, inc_db, full_db, "methods swapped", CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR), 0); + ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.W.Name"), ""); ASSERT_STR_EQ(go_reason(inc_db, "a/a.go", "b.V.Name"), ""); + ASSERT_STR_EQ(go_tier(inc_db, "a.Use", "b.W.Name"), "exact"); + ASSERT_STR_EQ(go_tier(inc_db, "a.Use", "b.V.Name"), "exact"); /* renaming a target */ go_w(repo, "b/b.go", GO_INC_B_RENAME); @@ -2006,10 +2047,20 @@ TEST(doc_mentions_go_scope_delta_rules) { GO_DELTA_HEAD GO_DELTA_F GO_DELTA_G GO_DELTA_T GO_DELTA_M GO_DELTA_I GO_DELTA_C "var (\n\tV = 2\n)\n", CBM_DOCLINK_DELTA_GLOBAL, NULL}, - /* the declaration that keeps a shared node name changes */ - {"same-named declarations swapped", twins, - "package p\n\nfunc (T) Open() {}\nfunc Open() {}\n", CBM_DOCLINK_DELTA_GLOBAL, NULL}, - {"one of two same-named declarations removed", twins, "package p\n\nfunc Open() {}\n", + /* a method and a function of one name no longer share a node + * (#1913): swapping them changes no keeper */ + {"method and function swapped", twins, "package p\n\nfunc (T) Open() {}\nfunc Open() {}\n", + CBM_DOCLINK_DELTA_LOCAL, ""}, + /* the declaration that keeps a shared node name changes: two + * package-level declarations of one name still share . */ + {"same-named declarations swapped", "package p\n\nfunc Open() {}\ntype Open struct{}\n", + "package p\n\ntype Open struct{}\nfunc Open() {}\n", CBM_DOCLINK_DELTA_GLOBAL, NULL}, + /* the method of a method/function pair removed: a member left, the + * function keeps its node either way */ + {"method of a same-named pair removed", twins, "package p\n\nfunc Open() {}\n", + CBM_DOCLINK_DELTA_LOCAL, "Open"}, + {"one of two same-named declarations removed", + "package p\n\nfunc Open() {}\ntype Open struct{}\n", "package p\n\nfunc Open() {}\n", CBM_DOCLINK_DELTA_GLOBAL, NULL}, /* other files follow `type X = q.Y` through this file's imports */ {"import changed under a qualified alias", alias, @@ -2091,7 +2142,10 @@ TEST(doc_mentions_go_workers_agree) { ASSERT_STR_EQ(go_tier(par_db, "p00.T0", "p01.T0"), "exact"); ASSERT_STR_EQ(go_reason(par_db, "p00/f0.go", "p01.Gone"), "missing"); ASSERT_STR_EQ(go_reason(par_db, "p00/f0.go", "fmt.Println"), "external"); - ASSERT_STR_EQ(go_reason(par_db, "p00/f0.go", "p01.T3.Close"), "graph_gap"); + /* #1913: [p01.T3.Close] lands on its own method node now; the graph_gap + * reason stays covered by Shared, one name declared by every file */ + ASSERT_STR_EQ(go_reason(par_db, "p00/f0.go", "p01.T3.Close"), ""); + ASSERT_STR_EQ(go_tier(par_db, "p00.T0", "p01.T3.Close"), "exact"); ASSERT_STR_EQ(go_reason(par_db, "p00/f3.go", "Shared"), "graph_gap"); ASSERT_TRUE(dm_count(par_db, "SELECT COUNT(*) FROM edges WHERE type = 'MENTIONS'") > 100); dm_unlink_db(par_db);