diff --git a/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json b/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json new file mode 100644 index 000000000..f13d5b020 --- /dev/null +++ b/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE builds SET output = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff" +} diff --git a/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json b/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json new file mode 100644 index 000000000..2dcdec9a3 --- /dev/null +++ b/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar\n FROM owners", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false + ] + }, + "hash": "073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41" +} diff --git a/.sqlx/query-09cfb32d3ce685cbb748a703fee395f8b9da75636522a343363c6e88a6e6dfb1.json b/.sqlx/query-09cfb32d3ce685cbb748a703fee395f8b9da75636522a343363c6e88a6e6dfb1.json new file mode 100644 index 000000000..9f5c2b08d --- /dev/null +++ b/.sqlx/query-09cfb32d3ce685cbb748a703fee395f8b9da75636522a343363c6e88a6e6dfb1.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT o.login\n FROM owners o, owner_rels r\n WHERE\n o.id = r.oid AND\n r.cid = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "09cfb32d3ce685cbb748a703fee395f8b9da75636522a343363c6e88a6e6dfb1" +} diff --git a/.sqlx/query-0a932ffd17414950513a2c8aca2ccd5e29780e00b105fbf79d2de83a11d33ddd.json b/.sqlx/query-0a932ffd17414950513a2c8aca2ccd5e29780e00b105fbf79d2de83a11d33ddd.json new file mode 100644 index 000000000..4c95721d1 --- /dev/null +++ b/.sqlx/query-0a932ffd17414950513a2c8aca2ccd5e29780e00b105fbf79d2de83a11d33ddd.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM owner_rels\n WHERE\n cid = $1 AND\n NOT (oid = ANY($2))", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4Array" + ] + }, + "nullable": [] + }, + "hash": "0a932ffd17414950513a2c8aca2ccd5e29780e00b105fbf79d2de83a11d33ddd" +} diff --git a/.sqlx/query-0ce07fe1eea10dafed3c3feb36010045c8cbcf443f6bfe78598bb75f05ecb249.json b/.sqlx/query-0ce07fe1eea10dafed3c3feb36010045c8cbcf443f6bfe78598bb75f05ecb249.json new file mode 100644 index 000000000..155502d5b --- /dev/null +++ b/.sqlx/query-0ce07fe1eea10dafed3c3feb36010045c8cbcf443f6bfe78598bb75f05ecb249.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n crates.id,\n crates.name,\n releases.default_target,\n releases.doc_targets\n FROM releases\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE crates.name = $1 AND releases.version = $2;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "default_target", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "doc_targets", + "type_info": "Json" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "0ce07fe1eea10dafed3c3feb36010045c8cbcf443f6bfe78598bb75f05ecb249" +} diff --git a/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json b/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json new file mode 100644 index 000000000..f7341988f --- /dev/null +++ b/.sqlx/query-0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n target_name,\n rustdoc_status\n FROM releases\n WHERE releases.id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "target_name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "rustdoc_status", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "0f51891df12ccdbecbdffef1588695c7a88206171881f2d9951eb2f40125b244" +} diff --git a/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json b/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json new file mode 100644 index 000000000..f1374e312 --- /dev/null +++ b/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO repositories (host, host_id, name, description, last_commit, stars, forks, issues, updated_at)\n VALUES ('github.com', $1, $2, 'Fake description!', NOW(), $3, $4, $5, NOW())\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Int4", + "Int4", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9" +} diff --git a/.sqlx/query-120c268463e7890553e639ac37e667f3fcdc3f5ceaab3c229b71dbb799c0cddc.json b/.sqlx/query-120c268463e7890553e639ac37e667f3fcdc3f5ceaab3c229b71dbb799c0cddc.json new file mode 100644 index 000000000..fa8db5d2f --- /dev/null +++ b/.sqlx/query-120c268463e7890553e639ac37e667f3fcdc3f5ceaab3c229b71dbb799c0cddc.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO compression_rels (release, algorithm)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING;", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "120c268463e7890553e639ac37e667f3fcdc3f5ceaab3c229b71dbb799c0cddc" +} diff --git a/.sqlx/query-1e660947261dfa1a5d1745d1732df59e0cf67ef1906da818086d063e6a0e21c6.json b/.sqlx/query-1e660947261dfa1a5d1745d1732df59e0cf67ef1906da818086d063e6a0e21c6.json new file mode 100644 index 000000000..e1bcf20ab --- /dev/null +++ b/.sqlx/query-1e660947261dfa1a5d1745d1732df59e0cf67ef1906da818086d063e6a0e21c6.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE crates\n SET latest_version_id = $2\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "1e660947261dfa1a5d1745d1732df59e0cf67ef1906da818086d063e6a0e21c6" +} diff --git a/.sqlx/query-38ca6c4bca81a0762e6e2db08a05ef0ba191d37199165d2745c64a4036e20790.json b/.sqlx/query-38ca6c4bca81a0762e6e2db08a05ef0ba191d37199165d2745c64a4036e20790.json new file mode 100644 index 000000000..c7752a36e --- /dev/null +++ b/.sqlx/query-38ca6c4bca81a0762e6e2db08a05ef0ba191d37199165d2745c64a4036e20790.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT slug FROM keywords WHERE slug = ANY($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "slug", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "TextArray" + ] + }, + "nullable": [ + false + ] + }, + "hash": "38ca6c4bca81a0762e6e2db08a05ef0ba191d37199165d2745c64a4036e20790" +} diff --git a/.sqlx/query-3aac87e4968ad4d83991de6b4ec164c44f00495b3a9a2e9b5ae460697a19278f.json b/.sqlx/query-3aac87e4968ad4d83991de6b4ec164c44f00495b3a9a2e9b5ae460697a19278f.json new file mode 100644 index 000000000..ab68991d0 --- /dev/null +++ b/.sqlx/query-3aac87e4968ad4d83991de6b4ec164c44f00495b3a9a2e9b5ae460697a19278f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO keyword_rels (rid, kid)\n SELECT $1 as rid, id as kid\n FROM keywords\n WHERE slug = ANY($2)\n ON CONFLICT DO NOTHING;", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "3aac87e4968ad4d83991de6b4ec164c44f00495b3a9a2e9b5ae460697a19278f" +} diff --git a/.sqlx/query-3b5412eff4406994b584d50e5aa30734fafc218f83ba856e2db6ddcfe423e72b.json b/.sqlx/query-3b5412eff4406994b584d50e5aa30734fafc218f83ba856e2db6ddcfe423e72b.json new file mode 100644 index 000000000..05620b5c5 --- /dev/null +++ b/.sqlx/query-3b5412eff4406994b584d50e5aa30734fafc218f83ba856e2db6ddcfe423e72b.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n crates.id AS crate_id\n FROM crates\n WHERE crates.name = $1;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "crate_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "3b5412eff4406994b584d50e5aa30734fafc218f83ba856e2db6ddcfe423e72b" +} diff --git a/.sqlx/query-3e9eb6292735559d232316d155c7dc463098408a0ca28f2b0c13bc6f4e3c32b6.json b/.sqlx/query-3e9eb6292735559d232316d155c7dc463098408a0ca28f2b0c13bc6f4e3c32b6.json new file mode 100644 index 000000000..fb5281405 --- /dev/null +++ b/.sqlx/query-3e9eb6292735559d232316d155c7dc463098408a0ca28f2b0c13bc6f4e3c32b6.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT o.login\n FROM owners o, owner_rels r\n WHERE\n o.id = r.oid AND\n r.cid = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "3e9eb6292735559d232316d155c7dc463098408a0ca28f2b0c13bc6f4e3c32b6" +} diff --git a/.sqlx/query-494e6b594aba915ba2582eca4797b13012576b2800c257ae3b0d3b7706c69a2c.json b/.sqlx/query-494e6b594aba915ba2582eca4797b13012576b2800c257ae3b0d3b7706c69a2c.json new file mode 100644 index 000000000..cf141db65 --- /dev/null +++ b/.sqlx/query-494e6b594aba915ba2582eca4797b13012576b2800c257ae3b0d3b7706c69a2c.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO doc_coverage (\n release_id, total_items, documented_items,\n total_items_needing_examples, items_with_examples\n )\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (release_id) DO UPDATE\n SET\n total_items = $2,\n documented_items = $3,\n total_items_needing_examples = $4,\n items_with_examples = $5\n RETURNING release_id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "release_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Int4", + "Int4", + "Int4", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "494e6b594aba915ba2582eca4797b13012576b2800c257ae3b0d3b7706c69a2c" +} diff --git a/.sqlx/query-58c1056e05fa7efdd691ad1c568bc00ec0f7c56584bdbe9d88963cc2f7ed7133.json b/.sqlx/query-58c1056e05fa7efdd691ad1c568bc00ec0f7c56584bdbe9d88963cc2f7ed7133.json new file mode 100644 index 000000000..cd88562f2 --- /dev/null +++ b/.sqlx/query-58c1056e05fa7efdd691ad1c568bc00ec0f7c56584bdbe9d88963cc2f7ed7133.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO keywords (name, slug) VALUES ($1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "58c1056e05fa7efdd691ad1c568bc00ec0f7c56584bdbe9d88963cc2f7ed7133" +} diff --git a/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json b/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json new file mode 100644 index 000000000..6200d4eb0 --- /dev/null +++ b/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO owners (login, avatar)\n VALUES ($1, $2)\n ON CONFLICT (login) DO UPDATE\n SET\n avatar = EXCLUDED.avatar\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932" +} diff --git a/.sqlx/query-5f31665c91028ac845408991812b704acf7b879860b7e5c55a1df04202e0ec65.json b/.sqlx/query-5f31665c91028ac845408991812b704acf7b879860b7e5c55a1df04202e0ec65.json new file mode 100644 index 000000000..60f1c5eab --- /dev/null +++ b/.sqlx/query-5f31665c91028ac845408991812b704acf7b879860b7e5c55a1df04202e0ec65.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [] + }, + "hash": "5f31665c91028ac845408991812b704acf7b879860b7e5c55a1df04202e0ec65" +} diff --git a/.sqlx/query-647d6a98ac5dfaa8d23bcab6687a343023ac052a6fda7ffc8124b70c67aa4b85.json b/.sqlx/query-647d6a98ac5dfaa8d23bcab6687a343023ac052a6fda7ffc8124b70c67aa4b85.json new file mode 100644 index 000000000..818018196 --- /dev/null +++ b/.sqlx/query-647d6a98ac5dfaa8d23bcab6687a343023ac052a6fda7ffc8124b70c67aa4b85.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, name\n FROM crates\n WHERE normalize_crate_name(name) = normalize_crate_name($1)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "647d6a98ac5dfaa8d23bcab6687a343023ac052a6fda7ffc8124b70c67aa4b85" +} diff --git a/.sqlx/query-7be4a44a1336e41023eca9b313d23adcafaeaeeb39322c565e59f3c4af79ca56.json b/.sqlx/query-7be4a44a1336e41023eca9b313d23adcafaeaeeb39322c565e59f3c4af79ca56.json new file mode 100644 index 000000000..009a16cc6 --- /dev/null +++ b/.sqlx/query-7be4a44a1336e41023eca9b313d23adcafaeaeeb39322c565e59f3c4af79ca56.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO crates (name) VALUES ($1) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "7be4a44a1336e41023eca9b313d23adcafaeaeeb39322c565e59f3c4af79ca56" +} diff --git a/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json b/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json new file mode 100644 index 000000000..84aff1df2 --- /dev/null +++ b/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar FROM owners", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false + ] + }, + "hash": "8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451" +} diff --git a/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json b/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json new file mode 100644 index 000000000..80e54a1b7 --- /dev/null +++ b/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM repositories", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f" +} diff --git a/.sqlx/query-95bc487e596e04e5e1cfd20903d8bdb54917c2ea91a0ee9c1dad7d5290ca2e62.json b/.sqlx/query-95bc487e596e04e5e1cfd20903d8bdb54917c2ea91a0ee9c1dad7d5290ca2e62.json new file mode 100644 index 000000000..312fd50c3 --- /dev/null +++ b/.sqlx/query-95bc487e596e04e5e1cfd20903d8bdb54917c2ea91a0ee9c1dad7d5290ca2e62.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO owner_rels (cid, oid)\n SELECT $1,oid\n FROM UNNEST($2::int[]) as oid\n ON CONFLICT (cid,oid)\n DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4Array" + ] + }, + "nullable": [] + }, + "hash": "95bc487e596e04e5e1cfd20903d8bdb54917c2ea91a0ee9c1dad7d5290ca2e62" +} diff --git a/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json b/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json new file mode 100644 index 000000000..513f77e80 --- /dev/null +++ b/.sqlx/query-99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n id,\n version,\n build_status,\n yanked,\n is_library,\n rustdoc_status,\n target_name\n FROM releases\n WHERE\n releases.crate_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "version", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "build_status", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "yanked", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "is_library", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "rustdoc_status", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "target_name", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "99e8ab430c8daa11932aef105538ed7d6199078f5189e41aadc5cf7256f15b93" +} diff --git a/.sqlx/query-af9e79bde5aefc6653257d46a04f29442314330fd6707886889dd109fd7540fe.json b/.sqlx/query-af9e79bde5aefc6653257d46a04f29442314330fd6707886889dd109fd7540fe.json new file mode 100644 index 000000000..d064df252 --- /dev/null +++ b/.sqlx/query-af9e79bde5aefc6653257d46a04f29442314330fd6707886889dd109fd7540fe.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM crates WHERE name = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "af9e79bde5aefc6653257d46a04f29442314330fd6707886889dd109fd7540fe" +} diff --git a/.sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json b/.sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json new file mode 100644 index 000000000..3d1324d1f --- /dev/null +++ b/.sqlx/query-ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e.json @@ -0,0 +1,221 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n crates.id AS crate_id,\n releases.id AS release_id,\n crates.name,\n releases.version,\n releases.description,\n releases.dependencies,\n releases.readme,\n releases.description_long,\n releases.release_time,\n releases.build_status,\n (\n SELECT id\n FROM builds\n WHERE\n builds.rid = releases.id AND\n builds.build_status = TRUE\n ORDER BY build_time DESC\n LIMIT 1\n ) AS latest_build_id,\n releases.rustdoc_status,\n releases.archive_storage,\n releases.repository_url,\n releases.homepage_url,\n releases.keywords,\n releases.have_examples,\n releases.target_name,\n repositories.host as \"repo_host?\",\n repositories.stars as \"repo_stars?\",\n repositories.forks as \"repo_forks?\",\n repositories.issues as \"repo_issues?\",\n repositories.name as \"repo_name?\",\n releases.is_library,\n releases.yanked,\n releases.doc_targets,\n releases.license,\n releases.documentation_url,\n releases.default_target,\n releases.doc_rustc_version,\n doc_coverage.total_items,\n doc_coverage.documented_items,\n doc_coverage.total_items_needing_examples,\n doc_coverage.items_with_examples\n FROM releases\n INNER JOIN crates ON releases.crate_id = crates.id\n LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id\n LEFT JOIN repositories ON releases.repository_id = repositories.id\n WHERE crates.name = $1 AND releases.version = $2;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "crate_id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "release_id", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "version", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "dependencies", + "type_info": "Json" + }, + { + "ordinal": 6, + "name": "readme", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "description_long", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "release_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 9, + "name": "build_status", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "latest_build_id", + "type_info": "Int4" + }, + { + "ordinal": 11, + "name": "rustdoc_status", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "archive_storage", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "repository_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "homepage_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "keywords", + "type_info": "Json" + }, + { + "ordinal": 16, + "name": "have_examples", + "type_info": "Bool" + }, + { + "ordinal": 17, + "name": "target_name", + "type_info": "Varchar" + }, + { + "ordinal": 18, + "name": "repo_host?", + "type_info": "Varchar" + }, + { + "ordinal": 19, + "name": "repo_stars?", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "repo_forks?", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "repo_issues?", + "type_info": "Int4" + }, + { + "ordinal": 22, + "name": "repo_name?", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "is_library", + "type_info": "Bool" + }, + { + "ordinal": 24, + "name": "yanked", + "type_info": "Bool" + }, + { + "ordinal": 25, + "name": "doc_targets", + "type_info": "Json" + }, + { + "ordinal": 26, + "name": "license", + "type_info": "Varchar" + }, + { + "ordinal": 27, + "name": "documentation_url", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "default_target", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "doc_rustc_version", + "type_info": "Varchar" + }, + { + "ordinal": 30, + "name": "total_items", + "type_info": "Int4" + }, + { + "ordinal": 31, + "name": "documented_items", + "type_info": "Int4" + }, + { + "ordinal": 32, + "name": "total_items_needing_examples", + "type_info": "Int4" + }, + { + "ordinal": 33, + "name": "items_with_examples", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + true, + true, + false, + false, + null, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + false, + true, + true, + true, + true + ] + }, + "hash": "ba20564dd8e17d01c5e30e429f5bd641aed9bbabb94ccc5877cd37ea1b540b5e" +} diff --git a/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json b/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json new file mode 100644 index 000000000..dd273b7db --- /dev/null +++ b/.sqlx/query-d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO builds (rid, rustc_version, docsrs_version, build_status, build_server)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Varchar", + "Varchar", + "Bool", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "d9f0166f4e46d6a601f0668606e1487eab21d8900c096f7e7e4629c210b3a371" +} diff --git a/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json b/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json new file mode 100644 index 000000000..709cac8c7 --- /dev/null +++ b/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar\n FROM owners\n INNER JOIN owner_rels ON owner_rels.oid = owners.id\n WHERE cid = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551" +} diff --git a/.sqlx/query-f418e582d28081864c3c571139a441dfc4f45a3f078cea8ae229531663376e7b.json b/.sqlx/query-f418e582d28081864c3c571139a441dfc4f45a3f078cea8ae229531663376e7b.json new file mode 100644 index 000000000..2f3ac6baf --- /dev/null +++ b/.sqlx/query-f418e582d28081864c3c571139a441dfc4f45a3f078cea8ae229531663376e7b.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login FROM owners order by login", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "f418e582d28081864c3c571139a441dfc4f45a3f078cea8ae229531663376e7b" +} diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs index abc694c1a..b87f8f868 100644 --- a/src/bin/cratesfyi.rs +++ b/src/bin/cratesfyi.rs @@ -552,15 +552,21 @@ impl DatabaseSubcommand { } Self::UpdateCrateRegistryFields { name } => { - db::update_crate_data_in_database( - &mut *ctx.conn()?, - &name, - &ctx.registry_api()?.get_crate_data(&name)?, - )?; + let registry_data = ctx.registry_api()?.get_crate_data(&name)?; + + ctx.runtime()?.block_on(async move { + let mut conn = ctx.pool()?.get_async().await?; + db::update_crate_data_in_database(&mut conn, &name, ®istry_data).await + })? } Self::AddDirectory { directory } => { - add_path_into_database(&*ctx.storage()?, &ctx.config()?.prefix, directory) + ctx.runtime()? + .block_on(async { + let storage = ctx.async_storage().await?; + + add_path_into_database(&storage, &ctx.config()?.prefix, directory).await + }) .context("Failed to add directory into database")?; } diff --git a/src/db/add_package.rs b/src/db/add_package.rs index 5ef8a12d7..940460e0a 100644 --- a/src/db/add_package.rs +++ b/src/db/add_package.rs @@ -8,7 +8,7 @@ use crate::{ web::crate_details::CrateDetails, }; use anyhow::{anyhow, Context}; -use postgres::Client; +use futures_util::stream::TryStreamExt; use serde_json::Value; use slug::slugify; use std::{ @@ -26,8 +26,8 @@ use tracing::{debug, info}; /// NOTE: `source_files` refers to the files originally in the crate, /// not the files generated by rustdoc. #[allow(clippy::too_many_arguments)] -pub(crate) fn add_package_into_database( - conn: &mut Client, +pub(crate) async fn add_package_into_database( + conn: &mut sqlx::PgConnection, metadata_pkg: &MetadataPackage, source_dir: &Path, res: &BuildResult, @@ -42,14 +42,14 @@ pub(crate) fn add_package_into_database( archive_storage: bool, ) -> Result { debug!("Adding package into database"); - let crate_id = initialize_package_in_database(conn, metadata_pkg)?; + let crate_id = initialize_package_in_database(conn, metadata_pkg).await?; let dependencies = convert_dependencies(metadata_pkg); let rustdoc = get_rustdoc(metadata_pkg, source_dir).unwrap_or(None); let readme = get_readme(metadata_pkg, source_dir).unwrap_or(None); let features = get_features(metadata_pkg); let is_library = metadata_pkg.is_library(); - let rows = conn.query( + let release_id: i32 = sqlx::query_scalar( "INSERT INTO releases ( crate_id, version, release_time, dependencies, target_name, yanked, build_status, @@ -92,69 +92,71 @@ pub(crate) fn add_package_into_database( repository_id = $26, archive_storage = $27 RETURNING id", - &[ - &crate_id, - &metadata_pkg.version, - ®istry_data.release_time, - &serde_json::to_value(dependencies)?, - &metadata_pkg.package_name(), - ®istry_data.yanked, - &res.successful, - &has_docs, - &false, // TODO: Add test status somehow - &metadata_pkg.license, - &metadata_pkg.repository, - &metadata_pkg.homepage, - &metadata_pkg.description, - &rustdoc, - &readme, - &serde_json::to_value(&metadata_pkg.keywords)?, - &has_examples, - ®istry_data.downloads, - &source_files, - &serde_json::to_value(doc_targets)?, - &is_library, - &res.rustc_version, - &metadata_pkg.documentation, - &default_target, - &features, - &repository_id, - &archive_storage, - ], - )?; - - let release_id: i32 = rows[0].get(0); - - add_keywords_into_database(conn, metadata_pkg, release_id)?; - add_compression_into_database(conn, compression_algorithms.into_iter(), release_id)?; + ) + .bind(crate_id) + .bind(&metadata_pkg.version) + .bind(registry_data.release_time) + .bind(serde_json::to_value(dependencies)?) + .bind(metadata_pkg.package_name()) + .bind(registry_data.yanked) + .bind(res.successful) + .bind(has_docs) + .bind(false) // TODO: Add test status somehow + .bind(&metadata_pkg.license) + .bind(&metadata_pkg.repository) + .bind(&metadata_pkg.homepage) + .bind(&metadata_pkg.description) + .bind(rustdoc) + .bind(readme) + .bind(serde_json::to_value(&metadata_pkg.keywords)?) + .bind(has_examples) + .bind(registry_data.downloads) + .bind(source_files) + .bind(serde_json::to_value(doc_targets)?) + .bind(is_library) + .bind(&res.rustc_version) + .bind(&metadata_pkg.documentation) + .bind(default_target) + .bind(features) + .bind(repository_id) + .bind(archive_storage) + .fetch_one(&mut *conn) + .await?; + + add_keywords_into_database(conn, metadata_pkg, release_id).await?; + add_compression_into_database(conn, compression_algorithms.into_iter(), release_id).await?; let crate_details = CrateDetails::new( - conn, + &mut *conn, &metadata_pkg.name, &metadata_pkg.version, &metadata_pkg.version, None, ) + .await .context("error when fetching crate-details")? .ok_or_else(|| anyhow!("crate details not found directly after creating them"))?; - conn.execute( + sqlx::query!( "UPDATE crates SET latest_version_id = $2 WHERE id = $1", - &[&crate_id, &crate_details.latest_release().id], - )?; + crate_id, + crate_details.latest_release().id, + ) + .execute(&mut *conn) + .await?; Ok(release_id) } -pub(crate) fn add_doc_coverage( - conn: &mut Client, +pub(crate) async fn add_doc_coverage( + conn: &mut sqlx::PgConnection, release_id: i32, doc_coverage: DocCoverage, ) -> Result { debug!("Adding doc coverage into database"); - let rows = conn.query( + Ok(sqlx::query_scalar!( "INSERT INTO doc_coverage ( release_id, total_items, documented_items, total_items_needing_examples, items_with_examples @@ -167,49 +169,56 @@ pub(crate) fn add_doc_coverage( total_items_needing_examples = $4, items_with_examples = $5 RETURNING release_id", - &[ - &release_id, - &doc_coverage.total_items, - &doc_coverage.documented_items, - &doc_coverage.total_items_needing_examples, - &doc_coverage.items_with_examples, - ], - )?; - Ok(rows[0].get(0)) + &release_id, + &doc_coverage.total_items, + &doc_coverage.documented_items, + &doc_coverage.total_items_needing_examples, + &doc_coverage.items_with_examples, + ) + .fetch_one(&mut *conn) + .await?) } /// Adds a build into database -pub(crate) fn add_build_into_database( - conn: &mut Client, +pub(crate) async fn add_build_into_database( + conn: &mut sqlx::PgConnection, release_id: i32, res: &BuildResult, ) -> Result { debug!("Adding build into database"); - let rows = conn.query( + let hostname = hostname::get()?; + Ok(sqlx::query_scalar!( "INSERT INTO builds (rid, rustc_version, docsrs_version, build_status, build_server) VALUES ($1, $2, $3, $4, $5) RETURNING id", - &[ - &release_id, - &res.rustc_version, - &res.docsrs_version, - &res.successful, - &hostname::get()?.to_str().unwrap_or(""), - ], - )?; - Ok(rows[0].get(0)) + release_id, + res.rustc_version, + res.docsrs_version, + res.successful, + hostname.to_str().unwrap_or(""), + ) + .fetch_one(&mut *conn) + .await?) } -fn initialize_package_in_database(conn: &mut Client, pkg: &MetadataPackage) -> Result { - let mut rows = conn.query("SELECT id FROM crates WHERE name = $1", &[&pkg.name])?; - // insert crate into database if it is not exists - if rows.is_empty() { - rows = conn.query( +async fn initialize_package_in_database( + conn: &mut sqlx::PgConnection, + pkg: &MetadataPackage, +) -> Result { + if let Some(id) = sqlx::query_scalar!("SELECT id FROM crates WHERE name = $1", pkg.name) + .fetch_optional(&mut *conn) + .await? + { + Ok(id) + } else { + // insert crate into database if it is not exists + Ok(sqlx::query_scalar!( "INSERT INTO crates (name) VALUES ($1) RETURNING id", - &[&pkg.name], - )?; + pkg.name, + ) + .fetch_one(&mut *conn) + .await?) } - Ok(rows[0].get(0)) } /// Convert dependencies into Vec<(String, String, String)> @@ -310,8 +319,8 @@ fn read_rust_doc(file_path: &Path) -> Result> { } /// Adds keywords into database -fn add_keywords_into_database( - conn: &mut Client, +async fn add_keywords_into_database( + conn: &mut sqlx::PgConnection, pkg: &MetadataPackage, release_id: i32, ) -> Result<()> { @@ -321,111 +330,134 @@ fn add_keywords_into_database( .map(|kw| (slugify(kw), kw.clone())) .collect(); - let existing_keyword_slugs: HashSet = conn - .query( - "SELECT slug FROM keywords WHERE slug = ANY($1)", - &[&wanted_keywords.keys().collect::>()], - )? - .iter() - .map(|row| row.get(0)) - .collect(); + let existing_keyword_slugs: HashSet = sqlx::query!( + "SELECT slug FROM keywords WHERE slug = ANY($1)", + &wanted_keywords.keys().cloned().collect::>()[..], + ) + .fetch(&mut *conn) + .map_ok(|row| row.slug) + .try_collect() + .await?; // we create new keywords one-by-one, since most of the time we already have them, // and because support for multi-record inserts is a mess without adding a new // library - let insert_keyword_query = conn.prepare("INSERT INTO keywords (name, slug) VALUES ($1, $2)")?; for (slug, name) in wanted_keywords .iter() .filter(|(k, _)| !(existing_keyword_slugs.contains(*k))) { - conn.execute(&insert_keyword_query, &[&name, &slug])?; + sqlx::query!( + "INSERT INTO keywords (name, slug) VALUES ($1, $2)", + name, + slug + ) + .execute(&mut *conn) + .await?; } - conn.execute( + sqlx::query!( "INSERT INTO keyword_rels (rid, kid) SELECT $1 as rid, id as kid FROM keywords WHERE slug = ANY($2) ON CONFLICT DO NOTHING;", - &[&release_id, &wanted_keywords.keys().collect::>()], - )?; + release_id, + &wanted_keywords.keys().cloned().collect::>()[..], + ) + .execute(&mut *conn) + .await?; Ok(()) } -pub fn update_crate_data_in_database( - conn: &mut Client, +pub async fn update_crate_data_in_database( + conn: &mut sqlx::PgConnection, name: &str, registry_data: &CrateData, ) -> Result<()> { info!("Updating crate data for {}", name); - let crate_id = conn - .query_one("SELECT id FROM crates WHERE crates.name = $1", &[&name])? - .get(0); + let crate_id = sqlx::query_scalar!("SELECT id FROM crates WHERE crates.name = $1", name) + .fetch_one(&mut *conn) + .await?; - update_owners_in_database(conn, ®istry_data.owners, crate_id)?; + update_owners_in_database(conn, ®istry_data.owners, crate_id).await?; Ok(()) } /// Adds owners into database -fn update_owners_in_database( - conn: &mut Client, +async fn update_owners_in_database( + conn: &mut sqlx::PgConnection, owners: &[CrateOwner], crate_id: i32, ) -> Result<()> { // Update any existing owner data since it is mutable and could have changed since last // time we pulled it - let owner_upsert = conn.prepare( - "INSERT INTO owners (login, avatar) - VALUES ($1, $2) - ON CONFLICT (login) DO UPDATE - SET - avatar = EXCLUDED.avatar - RETURNING id", - )?; - let oids: Vec = owners - .iter() - .map(|owner| -> Result<_> { - Ok(conn - .query_one(&owner_upsert, &[&owner.login, &owner.avatar])? - .get(0)) - }) - .collect::>>()?; + let mut oids: Vec = Vec::new(); + + for owner in owners { + oids.push( + sqlx::query_scalar!( + "INSERT INTO owners (login, avatar) + VALUES ($1, $2) + ON CONFLICT (login) DO UPDATE + SET + avatar = EXCLUDED.avatar + RETURNING id", + owner.login, + owner.avatar + ) + .fetch_one(&mut *conn) + .await?, + ); + } - conn.execute( + sqlx::query!( "INSERT INTO owner_rels (cid, oid) SELECT $1,oid FROM UNNEST($2::int[]) as oid ON CONFLICT (cid,oid) DO NOTHING", - &[&crate_id, &oids], - )?; + crate_id, + &oids[..] + ) + .execute(&mut *conn) + .await?; - conn.execute( + sqlx::query!( "DELETE FROM owner_rels WHERE cid = $1 AND NOT (oid = ANY($2))", - &[&crate_id, &oids], - )?; + crate_id, + &oids[..], + ) + .execute(&mut *conn) + .await?; Ok(()) } /// Add the compression algorithms used for this crate to the database -fn add_compression_into_database(conn: &mut Client, algorithms: I, release_id: i32) -> Result<()> +async fn add_compression_into_database( + conn: &mut sqlx::PgConnection, + algorithms: I, + release_id: i32, +) -> Result<()> where I: Iterator, { - let prepared = conn.prepare( - "INSERT INTO compression_rels (release, algorithm) - VALUES ($1, $2) - ON CONFLICT DO NOTHING;", - )?; for alg in algorithms { - conn.execute(&prepared, &[&release_id, &(alg as i32)])?; + sqlx::query!( + "INSERT INTO compression_rels (release, algorithm) + VALUES ($1, $2) + ON CONFLICT DO NOTHING;", + release_id, + &(alg as i32) + ) + .execute(&mut *conn) + .await?; } Ok(()) } @@ -552,40 +584,44 @@ mod test { #[test] fn new_owners() { - wrapper(|env| { - let mut conn = env.db().conn(); + async_wrapper(|env| async move { + let mut conn = env.async_db().await.async_conn().await; let crate_id = initialize_package_in_database( &mut conn, &MetadataPackage { ..Default::default() }, - )?; + ) + .await?; let owner1 = CrateOwner { avatar: "avatar".into(), login: "login".into(), }; - update_owners_in_database(&mut conn, &[owner1.clone()], crate_id)?; + update_owners_in_database(&mut conn, &[owner1.clone()], crate_id).await?; - let owner_def = conn.query_one( + let owner_def = sqlx::query!( "SELECT login, avatar - FROM owners", - &[], - )?; - assert_eq!(owner_def.get::<_, String>(0), owner1.login); - assert_eq!(owner_def.get::<_, String>(1), owner1.avatar); - - let owner_rel = conn.query_one( + FROM owners" + ) + .fetch_one(&mut *conn) + .await?; + assert_eq!(owner_def.login, owner1.login); + assert_eq!(owner_def.avatar, owner1.avatar); + + let owner_rel = sqlx::query!( "SELECT o.login FROM owners o, owner_rels r WHERE o.id = r.oid AND r.cid = $1", - &[&crate_id], - )?; - assert_eq!(owner_rel.get::<_, String>(0), owner1.login); + crate_id + ) + .fetch_one(&mut *conn) + .await?; + assert_eq!(owner_rel.login, owner1.login); Ok(()) }) @@ -593,9 +629,10 @@ mod test { #[test] fn update_owner_detais() { - wrapper(|env| { - let mut conn = env.db().conn(); - let crate_id = initialize_package_in_database(&mut conn, &MetadataPackage::default())?; + async_wrapper(|env| async move { + let mut conn = env.async_db().await.async_conn().await; + let crate_id = + initialize_package_in_database(&mut conn, &MetadataPackage::default()).await?; // set initial owner details update_owners_in_database( @@ -605,27 +642,32 @@ mod test { avatar: "avatar".into(), }], crate_id, - )?; + ) + .await?; let updated_owner = CrateOwner { login: "login".into(), avatar: "avatar2".into(), }; - update_owners_in_database(&mut conn, &[updated_owner.clone()], crate_id)?; + update_owners_in_database(&mut conn, &[updated_owner.clone()], crate_id).await?; - let owner_def = conn.query_one("SELECT login, avatar FROM owners", &[])?; - assert_eq!(owner_def.get::<_, String>(0), updated_owner.login); - assert_eq!(owner_def.get::<_, String>(1), updated_owner.avatar); + let owner_def = sqlx::query!("SELECT login, avatar FROM owners") + .fetch_one(&mut *conn) + .await?; + assert_eq!(owner_def.login, updated_owner.login); + assert_eq!(owner_def.avatar, updated_owner.avatar); - let owner_rel = conn.query_one( + let owner_rel = sqlx::query!( "SELECT o.login FROM owners o, owner_rels r WHERE o.id = r.oid AND r.cid = $1", - &[&crate_id], - )?; - assert_eq!(owner_rel.get::<_, String>(0), updated_owner.login); + crate_id + ) + .fetch_one(&mut *conn) + .await?; + assert_eq!(owner_rel.login, updated_owner.login); Ok(()) }) @@ -633,14 +675,15 @@ mod test { #[test] fn add_new_owners_and_delete_old() { - wrapper(|env| { - let mut conn = env.db().conn(); + async_wrapper(|env| async move { + let mut conn = env.async_db().await.async_conn().await; let crate_id = initialize_package_in_database( &mut conn, &MetadataPackage { ..Default::default() }, - )?; + ) + .await?; // set initial owner details update_owners_in_database( @@ -650,7 +693,8 @@ mod test { avatar: "avatar".into(), }], crate_id, - )?; + ) + .await?; let new_owners: Vec = (1..5) .map(|i| CrateOwner { @@ -659,13 +703,13 @@ mod test { }) .collect(); - update_owners_in_database(&mut conn, &new_owners, crate_id)?; + update_owners_in_database(&mut conn, &new_owners, crate_id).await?; - let all_owners: Vec = conn - .query("SELECT login FROM owners order by login", &[])? - .into_iter() - .map(|row| row.get::<_, String>(0)) - .collect(); + let all_owners: Vec = sqlx::query!("SELECT login FROM owners order by login") + .fetch(&mut *conn) + .map_ok(|row| row.login) + .try_collect() + .await?; // we still have all owners in the database. assert_eq!( @@ -673,18 +717,18 @@ mod test { vec!["login", "login1", "login2", "login3", "login4"] ); - let crate_owners: Vec = conn - .query( - "SELECT o.login - FROM owners o, owner_rels r - WHERE - o.id = r.oid AND - r.cid = $1", - &[&crate_id], - )? - .into_iter() - .map(|row| row.get::<_, String>(0)) - .collect(); + let crate_owners: Vec = sqlx::query!( + "SELECT o.login + FROM owners o, owner_rels r + WHERE + o.id = r.oid AND + r.cid = $1", + crate_id, + ) + .fetch(&mut *conn) + .map_ok(|row| row.login) + .try_collect() + .await?; // the owner-rel is deleted assert_eq!(crate_owners, vec!["login1", "login2", "login3", "login4"]); diff --git a/src/db/file.rs b/src/db/file.rs index 7ed3e219a..a134bf539 100644 --- a/src/db/file.rs +++ b/src/db/file.rs @@ -8,7 +8,7 @@ //! However, postgres is still available for testing and backwards compatibility. use crate::error::Result; -use crate::storage::{CompressionAlgorithm, CompressionAlgorithms, Storage}; +use crate::storage::{AsyncStorage, CompressionAlgorithm, CompressionAlgorithms}; use serde_json::Value; use std::path::{Path, PathBuf}; @@ -22,27 +22,29 @@ use std::path::{Path, PathBuf}; /// /// Note that this function is used for uploading both sources /// and files generated by rustdoc. -pub fn add_path_into_database>( - storage: &Storage, +pub async fn add_path_into_database>( + storage: &AsyncStorage, prefix: impl AsRef, path: P, ) -> Result<(Value, CompressionAlgorithms)> { - let (file_list, algorithms) = storage.store_all(prefix.as_ref(), path.as_ref())?; + let (file_list, algorithms) = storage.store_all(prefix.as_ref(), path.as_ref()).await?; Ok(( file_list_to_json(file_list.into_iter().collect()), algorithms, )) } -pub fn add_path_into_remote_archive>( - storage: &Storage, +pub async fn add_path_into_remote_archive>( + storage: &AsyncStorage, archive_path: &str, path: P, public_access: bool, ) -> Result<(Value, CompressionAlgorithm)> { - let (file_list, algorithm) = storage.store_all_in_archive(archive_path, path.as_ref())?; + let (file_list, algorithm) = storage + .store_all_in_archive(archive_path, path.as_ref()) + .await?; if public_access { - storage.set_public_access(archive_path, true)?; + storage.set_public_access(archive_path, true).await?; } Ok(( file_list_to_json(file_list.into_iter().collect()), diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index 9958c6682..49fb54681 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -13,7 +13,7 @@ use crate::utils::{ }; use crate::RUSTDOC_STATIC_STORAGE_PREFIX; use crate::{db::blacklist::is_blacklisted, utils::MetadataPackage}; -use crate::{Config, Context, InstanceMetrics, RegistryApi, Storage}; +use crate::{AsyncStorage, Config, Context, InstanceMetrics, RegistryApi, Storage}; use anyhow::{anyhow, bail, Context as _, Error}; use docsrs_metadata::{BuildTargets, Metadata, DEFAULT_TARGETS, HOST_TARGET}; use failure::Error as FailureError; @@ -88,6 +88,7 @@ pub struct RustwideBuilder { config: Arc, db: Pool, storage: Arc, + async_storage: Arc, metrics: Arc, registry_api: Arc, repository_stats_updater: Arc, @@ -98,14 +99,16 @@ impl RustwideBuilder { pub fn init(context: &dyn Context) -> Result { let config = context.config()?; let pool = context.pool()?; + let runtime = context.runtime()?; Ok(RustwideBuilder { workspace: build_workspace(context)?, toolchain: get_configured_toolchain(&mut *pool.get()?)?, config, db: pool, - runtime: context.runtime()?, + runtime: runtime.clone(), storage: context.storage()?, + async_storage: runtime.block_on(context.async_storage())?, metrics: context.instance_metrics()?, registry_api: context.registry_api()?, repository_stats_updater: context.repository_stats_updater()?, @@ -320,17 +323,17 @@ impl RustwideBuilder { // available at --static-root-path, we add files from that subdirectory, if present. let static_files = dest.as_ref().join("static.files"); if static_files.try_exists()? { - add_path_into_database( - &self.storage, + self.runtime.block_on(add_path_into_database( + &self.async_storage, RUSTDOC_STATIC_STORAGE_PREFIX, &static_files, - )?; + ))?; } else { - add_path_into_database( - &self.storage, + self.runtime.block_on(add_path_into_database( + &self.async_storage, RUSTDOC_STATIC_STORAGE_PREFIX, &dest, - )?; + ))?; } set_config(&mut conn, ConfigName::RustcVersion, rustc_version)?; @@ -499,24 +502,25 @@ impl RustwideBuilder { &metadata, )?; } - let (_, new_alg) = add_path_into_remote_archive( - &self.storage, + let (_, new_alg) = self.runtime.block_on(add_path_into_remote_archive( + &self.async_storage, &rustdoc_archive_path(name, version), local_storage.path(), true, - )?; + ))?; algs.insert(new_alg); }; // Store the sources even if the build fails debug!("adding sources into database"); let files_list = { - let (files_list, new_alg) = add_path_into_remote_archive( - &self.storage, - &source_archive_path(name, version), - build.host_source_dir(), - false, - )?; + let (files_list, new_alg) = + self.runtime.block_on(add_path_into_remote_archive( + &self.async_storage, + &source_archive_path(name, version), + build.host_source_dir(), + false, + ))?; algs.insert(new_alg); files_list }; @@ -551,8 +555,10 @@ impl RustwideBuilder { let cargo_metadata = res.cargo_metadata.root(); let repository = self.get_repo(cargo_metadata)?; - let release_id = add_package_into_database( - &mut conn, + let mut async_conn = self.runtime.block_on(self.db.get_async())?; + + let release_id = self.runtime.block_on(add_package_into_database( + &mut async_conn, cargo_metadata, &build.host_source_dir(), &res.result, @@ -565,22 +571,30 @@ impl RustwideBuilder { algs, repository, true, - )?; + ))?; if let Some(doc_coverage) = res.doc_coverage { - add_doc_coverage(&mut conn, release_id, doc_coverage)?; + self.runtime.block_on(add_doc_coverage( + &mut async_conn, + release_id, + doc_coverage, + ))?; } - let build_id = add_build_into_database(&mut conn, release_id, &res.result)?; + let build_id = self.runtime.block_on(add_build_into_database( + &mut async_conn, + release_id, + &res.result, + ))?; let build_log_path = format!("build-logs/{build_id}/{default_target}.txt"); self.storage.store_one(build_log_path, res.build_log)?; // Some crates.io crate data is mutable, so we proactively update it during a release if !is_local { match self.registry_api.get_crate_data(name) { - Ok(crate_data) => { - update_crate_data_in_database(&mut conn, name, &crate_data)? - } + Ok(crate_data) => self.runtime.block_on( + update_crate_data_in_database(&mut async_conn, name, &crate_data), + )?, Err(err) => warn!("{:#?}", err), } } @@ -596,6 +610,12 @@ impl RustwideBuilder { } } + self.runtime.block_on(async move { + // we need to drop the async connection inside an async runtime context + // so sqlx can use a runtime to handle the pool. + drop(async_conn); + }); + Ok(res.result.successful) })() .map_err(|e| failure::Error::from_boxed_compat(e.into())) diff --git a/src/test/fakes.rs b/src/test/fakes.rs index aa0e735a3..c6bfdb091 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -3,20 +3,24 @@ use super::TestDatabase; use crate::docbuilder::{BuildResult, DocCoverage}; use crate::error::Result; use crate::registry_api::{CrateData, CrateOwner, ReleaseData}; -use crate::storage::{rustdoc_archive_path, source_archive_path, Storage}; +use crate::storage::{ + rustdoc_archive_path, source_archive_path, AsyncStorage, CompressionAlgorithms, +}; use crate::utils::{Dependency, MetadataPackage, Target}; use anyhow::Context; use base64::{engine::general_purpose::STANDARD as b64, Engine}; use chrono::{DateTime, Utc}; -use postgres::Client; +use serde_json::Value; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use tokio::runtime::Runtime; use tracing::debug; #[must_use = "FakeRelease does nothing until you call .create()"] pub(crate) struct FakeRelease<'a> { db: &'a TestDatabase, - storage: Arc, + storage: Arc, + runtime: Arc, package: MetadataPackage, builds: Vec, /// name, content @@ -47,10 +51,15 @@ const DEFAULT_CONTENT: &[u8] = b"default content for test/fakes"; impl<'a> FakeRelease<'a> { - pub(super) fn new(db: &'a TestDatabase, storage: Arc) -> Self { + pub(super) fn new( + db: &'a TestDatabase, + storage: Arc, + runtime: Arc, + ) -> Self { FakeRelease { db, storage, + runtime, package: MetadataPackage { id: "fake-package-id".into(), name: "fake-package".into(), @@ -260,8 +269,13 @@ impl<'a> FakeRelease<'a> { self } + pub(crate) fn create(self) -> Result { + let runtime = self.runtime.clone(); + runtime.block_on(self.create_async()) + } + /// Returns the release_id - pub(crate) fn create(mut self) -> Result { + pub(crate) async fn create_async(mut self) -> Result { use std::fs; use std::path::Path; @@ -319,7 +333,13 @@ impl<'a> FakeRelease<'a> { Ok(()) }; - let upload_files = |kind: FileKind, source_directory: &Path| { + async fn upload_files( + kind: FileKind, + source_directory: &Path, + archive_storage: bool, + package: &MetadataPackage, + storage: &AsyncStorage, + ) -> Result<(Value, CompressionAlgorithms)> { debug!( "adding directory {:?} from {}", kind, @@ -336,11 +356,12 @@ impl<'a> FakeRelease<'a> { }; debug!("store in archive: {:?}", archive); let (files_list, new_alg) = crate::db::add_path_into_remote_archive( - &storage, + storage, &archive, source_directory, public, - )?; + ) + .await?; let mut hm = HashSet::new(); hm.insert(new_alg); Ok((files_list, hm)) @@ -350,12 +371,13 @@ impl<'a> FakeRelease<'a> { FileKind::Sources => "sources", }; crate::db::add_path_into_database( - &storage, + storage, format!("{}/{}/{}/", prefix, package.name, package.version), source_directory, ) + .await } - }; + } debug!("before upload source"); let source_tmp = create_temp_dir(); @@ -378,7 +400,14 @@ impl<'a> FakeRelease<'a> { store_files_into(&[("Cargo.toml", content.as_bytes())], source_tmp.path())?; } - let (source_meta, algs) = upload_files(FileKind::Sources, source_tmp.path())?; + let (source_meta, algs) = upload_files( + FileKind::Sources, + source_tmp.path(), + archive_storage, + &package, + &storage, + ) + .await?; debug!("added source files {}", source_meta); // If the test didn't add custom builds, inject a default one @@ -409,12 +438,21 @@ impl<'a> FakeRelease<'a> { debug!("added platform files for {}", platform); } - let (rustdoc_meta, _) = upload_files(FileKind::Rustdoc, rustdoc_path)?; + let (rustdoc_meta, _) = upload_files( + FileKind::Rustdoc, + rustdoc_path, + archive_storage, + &package, + &storage, + ) + .await?; debug!("uploaded rustdoc files: {}", rustdoc_meta); } + let mut async_conn = db.async_conn().await; + let repository = match self.github_stats { - Some(stats) => Some(stats.create(&mut self.db.conn())?), + Some(stats) => Some(stats.create(&mut async_conn).await?), None => None, }; @@ -428,8 +466,9 @@ impl<'a> FakeRelease<'a> { // be set to docsrs_metadata::HOST_TARGET, because then tests fail on all // non-linux platforms. let default_target = self.default_target.unwrap_or("x86_64-unknown-linux-gnu"); + let mut async_conn = db.async_conn().await; let release_id = crate::db::add_package_into_database( - &mut db.conn(), + &mut async_conn, &package, crate_dir, last_build_result, @@ -442,17 +481,21 @@ impl<'a> FakeRelease<'a> { algs, repository, archive_storage, - )?; + ) + .await?; crate::db::update_crate_data_in_database( - &mut db.conn(), + &mut async_conn, &package.name, &self.registry_crate_data, - )?; + ) + .await?; for build in &self.builds { - build.create(&mut db.conn(), &storage, release_id, default_target)?; + build + .create(&mut async_conn, &storage, release_id, default_target) + .await?; } if let Some(coverage) = self.doc_coverage { - crate::db::add_doc_coverage(&mut db.conn(), release_id, coverage)?; + crate::db::add_doc_coverage(&mut async_conn, release_id, coverage).await?; } Ok(release_id) @@ -467,20 +510,21 @@ struct FakeGithubStats { } impl FakeGithubStats { - fn create(&self, conn: &mut Client) -> Result { - let existing_count: i64 = conn - .query_one("SELECT COUNT(*) FROM repositories;", &[])? - .get(0); + async fn create(&self, conn: &mut sqlx::PgConnection) -> Result { + let existing_count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM repositories") + .fetch_one(&mut *conn) + .await? + .unwrap(); let host_id = b64.encode(format!("FAKE ID {existing_count}")); - let data = conn.query_one( + let id = sqlx::query_scalar!( "INSERT INTO repositories (host, host_id, name, description, last_commit, stars, forks, issues, updated_at) VALUES ('github.com', $1, $2, 'Fake description!', NOW(), $3, $4, $5, NOW()) - RETURNING id;", - &[&host_id, &self.repo, &self.stars, &self.forks, &self.issues], - )?; + RETURNING id", + host_id, self.repo, self.stars, self.forks, self.issues, + ).fetch_one(&mut *conn).await?; - Ok(data.get(0)) + Ok(id) } } @@ -536,25 +580,29 @@ impl FakeBuild { } } - fn create( + async fn create( &self, - conn: &mut Client, - storage: &Storage, + conn: &mut sqlx::PgConnection, + storage: &AsyncStorage, release_id: i32, default_target: &str, ) -> Result<()> { - let build_id = crate::db::add_build_into_database(conn, release_id, &self.result)?; + let build_id = + crate::db::add_build_into_database(&mut *conn, release_id, &self.result).await?; if let Some(db_build_log) = self.db_build_log.as_deref() { - conn.query( + sqlx::query!( "UPDATE builds SET output = $2 WHERE id = $1", - &[&build_id, &db_build_log], - )?; + build_id, + db_build_log + ) + .execute(&mut *conn) + .await?; } if let Some(s3_build_log) = self.s3_build_log.as_deref() { let path = format!("build-logs/{build_id}/{default_target}.txt"); - storage.store_one(path, s3_build_log)?; + storage.store_one(path, s3_build_log).await?; } Ok(()) diff --git a/src/test/mod.rs b/src/test/mod.rs index c6df8bc0a..16ca68d13 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -515,7 +515,15 @@ impl TestEnvironment { } pub(crate) fn fake_release(&self) -> fakes::FakeRelease { - fakes::FakeRelease::new(self.db(), self.storage()) + self.runtime().block_on(self.async_fake_release()) + } + + pub(crate) async fn async_fake_release(&self) -> fakes::FakeRelease { + fakes::FakeRelease::new( + self.async_db().await, + self.async_storage().await, + self.runtime(), + ) } } diff --git a/src/web/builds.rs b/src/web/builds.rs index 08c1f2901..0c8e16b49 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,9 +1,8 @@ use super::{cache::CachePolicy, headers::CanonicalUrl, MatchSemver}; use crate::{ - db::Pool, docbuilder::Limits, impl_axum_webpage, - web::{error::AxumResult, match_version_axum, MetaData}, + web::{error::AxumResult, extractors::DbConnection, match_version, MetaData}, Config, }; use anyhow::Result; @@ -41,10 +40,10 @@ impl_axum_webpage! { pub(crate) async fn build_list_handler( Path((name, req_version)): Path<(String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, Extension(config): Extension>, ) -> AxumResult { - let (version, version_or_latest) = match match_version_axum(&pool, &name, Some(&req_version)) + let (version, version_or_latest) = match match_version(&mut conn, &name, Some(&req_version)) .await? .exact_name_only()? { @@ -60,8 +59,6 @@ pub(crate) async fn build_list_handler( } }; - let mut conn = pool.get_async().await?; - Ok(BuildsPage { metadata: MetaData::from_crate(&mut conn, &name, &version, &version_or_latest).await?, builds: get_builds(&mut conn, &name, &version).await?, @@ -74,9 +71,9 @@ pub(crate) async fn build_list_handler( pub(crate) async fn build_list_json_handler( Path((name, req_version)): Path<(String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, ) -> AxumResult { - let version = match match_version_axum(&pool, &name, Some(&req_version)) + let version = match match_version(&mut conn, &name, Some(&req_version)) .await? .exact_name_only()? { @@ -90,8 +87,6 @@ pub(crate) async fn build_list_json_handler( } }; - let mut conn = pool.get_async().await?; - Ok(( Extension(CachePolicy::NoStoreMustRevalidate), [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index c4d8a28e4..f0573d3b3 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -1,9 +1,8 @@ use super::{markdown, match_version, MatchSemver, MetaData}; -use crate::utils::{get_correct_docsrs_style_file, report_error, spawn_blocking}; +use crate::utils::{get_correct_docsrs_style_file, report_error}; +use crate::web::axum_cached_redirect; use crate::web::rustdoc::RustdocHtmlParams; -use crate::web::{axum_cached_redirect, match_version_axum}; use crate::{ - db::Pool, impl_axum_webpage, repositories::RepositoryStatsUpdater, storage::PathNotFoundError, @@ -11,6 +10,7 @@ use crate::{ cache::CachePolicy, encode_url_path, error::{AxumNope, AxumResult}, + extractors::DbConnection, }, AsyncStorage, }; @@ -20,8 +20,8 @@ use axum::{ response::{IntoResponse, Response as AxumResponse}, }; use chrono::{DateTime, Utc}; +use futures_util::stream::TryStreamExt; use log::warn; -use postgres::GenericClient; use serde::Deserialize; use serde::{ser::Serializer, Serialize}; use serde_json::Value; @@ -99,16 +99,15 @@ pub struct Release { } impl CrateDetails { - pub fn new( - conn: &mut impl GenericClient, + pub async fn new( + conn: &mut sqlx::PgConnection, name: &str, version: &str, version_or_latest: &str, up: Option<&RepositoryStatsUpdater>, ) -> Result, anyhow::Error> { - // get all stuff, I love you rustfmt - let query = " - SELECT + let krate = match sqlx::query!( + r#"SELECT crates.id AS crate_id, releases.id AS release_id, crates.name, @@ -135,11 +134,11 @@ impl CrateDetails { releases.keywords, releases.have_examples, releases.target_name, - repositories.host as repo_host, - repositories.stars as repo_stars, - repositories.forks as repo_forks, - repositories.issues as repo_issues, - repositories.name as repo_name, + repositories.host as "repo_host?", + repositories.stars as "repo_stars?", + repositories.forks as "repo_forks?", + repositories.issues as "repo_issues?", + repositories.name as "repo_name?", releases.is_library, releases.yanked, releases.doc_targets, @@ -155,89 +154,86 @@ impl CrateDetails { INNER JOIN crates ON releases.crate_id = crates.id LEFT JOIN doc_coverage ON doc_coverage.release_id = releases.id LEFT JOIN repositories ON releases.repository_id = repositories.id - WHERE crates.name = $1 AND releases.version = $2;"; - - let krate = match conn.query_opt(query, &[&name, &version])? { + WHERE crates.name = $1 AND releases.version = $2;"#, + name, + version + ) + .fetch_optional(&mut *conn) + .await? + { Some(row) => row, None => return Ok(None), }; - let crate_id: i32 = krate.get("crate_id"); - let release_id: i32 = krate.get("release_id"); - // get releases, sorted by semver - let releases = releases_for_crate(conn, crate_id)?; - - let repository_metadata = - krate - .get::<_, Option>("repo_host") - .map(|host| RepositoryMetadata { - issues: krate.get("repo_issues"), - stars: krate.get("repo_stars"), - forks: krate.get("repo_forks"), - name: krate.get("repo_name"), - icon: up.map_or("code-branch", |u| u.get_icon_name(&host)), - }); + let releases = releases_for_crate(conn, krate.crate_id).await?; + + let repository_metadata = krate.repo_host.map(|host| RepositoryMetadata { + issues: krate.repo_issues.unwrap(), + stars: krate.repo_stars.unwrap(), + forks: krate.repo_forks.unwrap(), + name: krate.repo_name, + icon: up.map_or("code-branch", |u| u.get_icon_name(&host)), + }); let metadata = MetaData { - name: krate.get("name"), - version: krate.get("version"), + name: krate.name.clone(), + version: krate.version.clone(), version_or_latest: version_or_latest.to_string(), - description: krate.get("description"), - rustdoc_status: krate.get("rustdoc_status"), - target_name: krate.get("target_name"), - default_target: krate.get("default_target"), - doc_targets: MetaData::parse_doc_targets(krate.get("doc_targets")), - yanked: krate.get("yanked"), - rustdoc_css_file: get_correct_docsrs_style_file(krate.get("doc_rustc_version"))?, + description: krate.description.clone(), + rustdoc_status: krate.rustdoc_status, + target_name: Some(krate.target_name.clone()), + default_target: krate.default_target, + doc_targets: MetaData::parse_doc_targets(krate.doc_targets), + yanked: krate.yanked, + rustdoc_css_file: get_correct_docsrs_style_file(&krate.doc_rustc_version)?, }; let mut crate_details = CrateDetails { - name: krate.get("name"), - version: krate.get("version"), - description: krate.get("description"), + name: krate.name, + version: krate.version, + description: krate.description, owners: Vec::new(), - dependencies: krate.get("dependencies"), - readme: krate.get("readme"), - rustdoc: krate.get("description_long"), - release_time: krate.get("release_time"), - build_status: krate.get("build_status"), - latest_build_id: krate.get("latest_build_id"), + dependencies: krate.dependencies, + readme: krate.readme, + rustdoc: krate.description_long, + release_time: krate.release_time, + build_status: krate.build_status, + latest_build_id: krate.latest_build_id, last_successful_build: None, - rustdoc_status: krate.get("rustdoc_status"), - archive_storage: krate.get("archive_storage"), - repository_url: krate.get("repository_url"), - homepage_url: krate.get("homepage_url"), - keywords: krate.get("keywords"), - have_examples: krate.get("have_examples"), - target_name: krate.get("target_name"), + rustdoc_status: krate.rustdoc_status, + archive_storage: krate.archive_storage, + repository_url: krate.repository_url, + homepage_url: krate.homepage_url, + keywords: krate.keywords, + have_examples: krate.have_examples, + target_name: krate.target_name, releases, repository_metadata, metadata, - is_library: krate.get("is_library"), - license: krate.get("license"), - documentation_url: krate.get("documentation_url"), - documented_items: krate.get("documented_items"), - total_items: krate.get("total_items"), - total_items_needing_examples: krate.get("total_items_needing_examples"), - items_with_examples: krate.get("items_with_examples"), - crate_id, - release_id, + is_library: krate.is_library, + license: krate.license, + documentation_url: krate.documentation_url, + documented_items: krate.documented_items, + total_items: krate.total_items, + total_items_needing_examples: krate.total_items_needing_examples, + items_with_examples: krate.items_with_examples, + crate_id: krate.crate_id, + release_id: krate.release_id, }; // get owners - let owners = conn.query( + crate_details.owners = sqlx::query!( "SELECT login, avatar - FROM owners - INNER JOIN owner_rels ON owner_rels.oid = owners.id - WHERE cid = $1", - &[&crate_id], - )?; - - crate_details.owners = owners - .into_iter() - .map(|row| (row.get("login"), row.get("avatar"))) - .collect(); + FROM owners + INNER JOIN owner_rels ON owner_rels.oid = owners.id + WHERE cid = $1", + krate.crate_id, + ) + .fetch(&mut *conn) + .map_ok(|row| (row.login, row.avatar)) + .try_collect() + .await?; if !crate_details.build_status { crate_details.last_successful_build = crate_details @@ -319,47 +315,51 @@ impl CrateDetails { } /// Return all releases for a crate, sorted in descending order by semver -pub(crate) fn releases_for_crate( - conn: &mut impl GenericClient, +pub(crate) async fn releases_for_crate( + conn: &mut sqlx::PgConnection, crate_id: i32, ) -> Result, anyhow::Error> { - let mut releases: Vec = conn - .query( - "SELECT - id, - version, - build_status, - yanked, - is_library, - rustdoc_status, - target_name - FROM releases - WHERE - releases.crate_id = $1", - &[&crate_id], - )? - .into_iter() - .filter_map(|row| { - let version: String = row.get("version"); - match semver::Version::parse(&version).with_context(|| { - format!("invalid semver in database for crate {crate_id}: {version}") + let mut releases: Vec = sqlx::query!( + "SELECT + id, + version, + build_status, + yanked, + is_library, + rustdoc_status, + target_name + FROM releases + WHERE + releases.crate_id = $1", + crate_id, + ) + .fetch(&mut *conn) + .try_filter_map(|row| async move { + Ok( + match semver::Version::parse(&row.version).with_context(|| { + format!( + "invalid semver in database for crate {crate_id}: {}", + row.version + ) }) { Ok(semversion) => Some(Release { - id: row.get("id"), + id: row.id, version: semversion, - build_status: row.get("build_status"), - yanked: row.get("yanked"), - is_library: row.get("is_library"), - rustdoc_status: row.get("rustdoc_status"), - target_name: row.get("target_name"), + build_status: row.build_status, + yanked: row.yanked, + is_library: row.is_library, + rustdoc_status: row.rustdoc_status, + target_name: row.target_name, }), Err(err) => { report_error(&err); None } - } - }) - .collect(); + }, + ) + }) + .try_collect() + .await?; releases.sort_by(|a, b| b.version.cmp(&a.version)); Ok(releases) @@ -381,11 +381,11 @@ pub(crate) struct CrateDetailHandlerParams { version: Option, } -#[tracing::instrument(skip(pool, storage))] +#[tracing::instrument(skip(conn, storage))] pub(crate) async fn crate_details_handler( Path(params): Path, Extension(storage): Extension>, - Extension(pool): Extension, + mut conn: DbConnection, Extension(repository_stats_updater): Extension>, ) -> AxumResult { // this handler must always called with a crate name @@ -397,18 +397,9 @@ pub(crate) async fn crate_details_handler( .into_response()); } - let found_version = spawn_blocking({ - let pool = pool.clone(); - let params = params.clone(); - move || { - let mut conn = pool.get()?; - Ok( - match_version(&mut conn, ¶ms.name, params.version.as_deref()) - .and_then(|m| m.exact_name_only())?, - ) - } - }) - .await?; + let found_version = match_version(&mut conn, ¶ms.name, params.version.as_deref()) + .await + .and_then(|m| m.exact_name_only())?; let (version, version_or_latest, is_latest_url) = match found_version { MatchSemver::Exact((version, _)) => (version.clone(), version, false), @@ -422,16 +413,13 @@ pub(crate) async fn crate_details_handler( } }; - let mut details = spawn_blocking(move || { - let mut conn = pool.get()?; - CrateDetails::new( - &mut *conn, - ¶ms.name, - &version, - &version_or_latest, - Some(&repository_stats_updater), - ) - }) + let mut details = CrateDetails::new( + &mut conn, + ¶ms.name, + &version, + &version_or_latest, + Some(&repository_stats_updater), + ) .await? .ok_or(AxumNope::VersionNotFound)?; @@ -465,32 +453,25 @@ impl_axum_webpage! { #[tracing::instrument] pub(crate) async fn get_all_releases( Path(params): Path, - Extension(pool): Extension, + mut conn: DbConnection, ) -> AxumResult { - let releases: Vec = spawn_blocking({ - let pool = pool.clone(); - let params = params.clone(); - move || { - let mut conn = pool.get()?; - let query = " - SELECT - crates.id AS crate_id - FROM crates - WHERE crates.name = $1;"; - - let rows = conn.query(query, &[¶ms.name])?; - - let result = if rows.is_empty() { - return Ok(Vec::new()); - } else { - &rows[0] - }; - // get releases, sorted by semver - releases_for_crate(&mut *conn, result.get("crate_id")) - } - }) + let crate_id = sqlx::query_scalar!( + "SELECT + crates.id AS crate_id + FROM crates + WHERE crates.name = $1;", + params.name, + ) + .fetch_optional(&mut *conn) .await?; + let releases: Vec = if let Some(crate_id) = crate_id { + // get releases, sorted by semver + releases_for_crate(&mut conn, crate_id).await? + } else { + Vec::new() + }; + let res = ReleaseList { releases, crate_name: params.name, @@ -522,13 +503,13 @@ impl_axum_webpage! { #[tracing::instrument] pub(crate) async fn get_all_platforms_inner( Path(params): Path, - Extension(pool): Extension, + mut conn: DbConnection, is_crate_root: bool, ) -> AxumResult { let req_path: String = params.path.unwrap_or_default(); let req_path: Vec<&str> = req_path.split('/').collect(); - let release_found = match_version_axum(&pool, ¶ms.name, Some(¶ms.version)).await?; + let release_found = match_version(&mut conn, ¶ms.name, Some(¶ms.version)).await?; trace!(?release_found, "found release"); // Convenience function to allow for easy redirection @@ -576,41 +557,25 @@ pub(crate) async fn get_all_platforms_inner( } }; - let (name, doc_targets, releases, default_target): (String, Vec, Vec, String) = - spawn_blocking({ - let pool = pool.clone(); - move || { - let mut conn = pool.get()?; - let query = " - SELECT - crates.id, - crates.name, - releases.default_target, - releases.doc_targets - FROM releases - INNER JOIN crates ON releases.crate_id = crates.id - WHERE crates.name = $1 AND releases.version = $2;"; - - let rows = conn.query(query, &[¶ms.name, &version])?; - - let krate = if rows.is_empty() { - return Err(AxumNope::CrateNotFound.into()); - } else { - &rows[0] - }; + let krate = sqlx::query!( + "SELECT + crates.id, + crates.name, + releases.default_target, + releases.doc_targets + FROM releases + INNER JOIN crates ON releases.crate_id = crates.id + WHERE crates.name = $1 AND releases.version = $2;", + params.name, + version + ) + .fetch_optional(&mut *conn) + .await? + .ok_or(AxumNope::CrateNotFound)?; - // get releases, sorted by semver - let releases = releases_for_crate(&mut *conn, krate.get("id"))?; + let releases = releases_for_crate(&mut conn, krate.id).await?; - Ok(( - krate.get("name"), - MetaData::parse_doc_targets(krate.get("doc_targets")), - releases, - krate.get("default_target"), - )) - } - }) - .await?; + let doc_targets = MetaData::parse_doc_targets(krate.doc_targets); let latest_release = releases .iter() @@ -637,14 +602,14 @@ pub(crate) async fn get_all_platforms_inner( (target, inner.trim_end_matches('/')) }; let inner_path = if inner_path.is_empty() { - format!("{name}/index.html") + format!("{}/index.html", krate.name) } else { - format!("{name}/{inner_path}") + format!("{}/{inner_path}", krate.name) }; let current_target = if latest_release.build_status { if target.is_empty() { - default_target + krate.default_target } else { target.to_owned() } @@ -654,7 +619,7 @@ pub(crate) async fn get_all_platforms_inner( let res = PlatformList { metadata: ShortMetadata { - name, + name: krate.name, version_or_latest: version_or_latest.to_string(), doc_targets, }, @@ -667,17 +632,17 @@ pub(crate) async fn get_all_platforms_inner( pub(crate) async fn get_all_platforms_root( Path(mut params): Path, - pool: Extension, + conn: DbConnection, ) -> AxumResult { params.path = None; - get_all_platforms_inner(Path(params), pool, true).await + get_all_platforms_inner(Path(params), conn, true).await } pub(crate) async fn get_all_platforms( params: Path, - pool: Extension, + conn: DbConnection, ) -> AxumResult { - get_all_platforms_inner(params, pool, false).await + get_all_platforms_inner(params, conn, false).await } #[cfg(test)] @@ -685,20 +650,22 @@ mod tests { use super::*; use crate::registry_api::CrateOwner; use crate::test::{ - assert_cache_control, assert_redirect, assert_redirect_cached, wrapper, TestDatabase, - TestEnvironment, + assert_cache_control, assert_redirect, assert_redirect_cached, async_wrapper, wrapper, + TestDatabase, TestEnvironment, }; use anyhow::{Context, Error}; use kuchikiki::traits::TendrilSink; use std::collections::HashMap; - fn assert_last_successful_build_equals( + async fn assert_last_successful_build_equals( db: &TestDatabase, package: &str, version: &str, expected_last_successful_build: Option<&str>, ) -> Result<(), Error> { - let details = CrateDetails::new(&mut *db.conn(), package, version, version, None) + let mut conn = db.async_conn().await; + let details = CrateDetails::new(&mut conn, package, version, version, None) + .await .with_context(|| anyhow::anyhow!("could not fetch crate details"))? .unwrap(); @@ -711,87 +678,123 @@ mod tests { #[test] fn test_last_successful_build_when_last_releases_failed_or_yanked() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; - env.fake_release().name("foo").version("0.0.1").create()?; - env.fake_release().name("foo").version("0.0.2").create()?; - env.fake_release() + env.async_fake_release() + .await + .name("foo") + .version("0.0.1") + .create_async() + .await?; + env.async_fake_release() + .await + .name("foo") + .version("0.0.2") + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.3") .build_result_failed() - .create()?; - env.fake_release() + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.4") .yanked(true) - .create()?; - env.fake_release() + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.5") .build_result_failed() .yanked(true) - .create()?; - - assert_last_successful_build_equals(db, "foo", "0.0.1", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.2", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.3", Some("0.0.2"))?; - assert_last_successful_build_equals(db, "foo", "0.0.4", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.5", Some("0.0.2"))?; + .create_async() + .await?; + + assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.3", Some("0.0.2")).await?; + assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.5", Some("0.0.2")).await?; Ok(()) }); } #[test] fn test_last_successful_build_when_all_releases_failed_or_yanked() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; - env.fake_release() + env.async_fake_release() + .await .name("foo") .version("0.0.1") .build_result_failed() - .create()?; - env.fake_release() + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.2") .build_result_failed() - .create()?; - env.fake_release() + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.3") .yanked(true) - .create()?; + .create_async() + .await?; - assert_last_successful_build_equals(db, "foo", "0.0.1", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.2", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.3", None)?; + assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; Ok(()) }); } #[test] fn test_last_successful_build_with_intermittent_releases_failed_or_yanked() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; - env.fake_release().name("foo").version("0.0.1").create()?; - env.fake_release() + env.async_fake_release() + .await + .name("foo") + .version("0.0.1") + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.2") .build_result_failed() - .create()?; - env.fake_release() + .create_async() + .await?; + env.async_fake_release() + .await .name("foo") .version("0.0.3") .yanked(true) - .create()?; - env.fake_release().name("foo").version("0.0.4").create()?; + .create_async() + .await?; + env.async_fake_release() + .await + .name("foo") + .version("0.0.4") + .create_async() + .await?; - assert_last_successful_build_equals(db, "foo", "0.0.1", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.2", Some("0.0.4"))?; - assert_last_successful_build_equals(db, "foo", "0.0.3", None)?; - assert_last_successful_build_equals(db, "foo", "0.0.4", None)?; + assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.2", Some("0.0.4")).await?; + assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; + assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; Ok(()) }); } @@ -827,9 +830,14 @@ mod tests { .binary(true) .create()?; - let details = CrateDetails::new(&mut *db.conn(), "foo", "0.2.0", "0.2.0", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", "0.2.0", "0.2.0", None) + .await + .unwrap() + .unwrap() + }); + assert_eq!( details.releases, vec![ @@ -943,9 +951,13 @@ mod tests { env.fake_release().name("foo").version("0.0.2").create()?; for version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = CrateDetails::new(&mut *db.conn(), "foo", version, version, None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", version, version, None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.latest_release().version, semver::Version::parse("0.0.3")? @@ -969,9 +981,13 @@ mod tests { env.fake_release().name("foo").version("0.0.2").create()?; for version in &["0.0.1", "0.0.2", "0.0.3-pre.1"] { - let details = CrateDetails::new(&mut *db.conn(), "foo", version, version, None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", version, version, None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.latest_release().version, semver::Version::parse("0.0.2")? @@ -996,9 +1012,13 @@ mod tests { env.fake_release().name("foo").version("0.0.2").create()?; for version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = CrateDetails::new(&mut *db.conn(), "foo", version, version, None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", version, version, None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.latest_release().version, semver::Version::parse("0.0.2")? @@ -1031,9 +1051,13 @@ mod tests { .create()?; for version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = CrateDetails::new(&mut *db.conn(), "foo", version, version, None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", version, version, None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.latest_release().version, semver::Version::parse("0.0.3")? @@ -1087,9 +1111,13 @@ mod tests { }) .create()?; - let details = CrateDetails::new(&mut *db.conn(), "foo", "0.0.1", "0.0.1", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", "0.0.1", "0.0.1", None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.owners, vec![("foobar".into(), "https://example.org/foobar".into())] @@ -1109,9 +1137,13 @@ mod tests { }) .create()?; - let details = CrateDetails::new(&mut *db.conn(), "foo", "0.0.1", "0.0.1", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", "0.0.1", "0.0.1", None) + .await + .unwrap() + .unwrap() + }); let mut owners = details.owners; owners.sort(); assert_eq!( @@ -1132,9 +1164,13 @@ mod tests { }) .create()?; - let details = CrateDetails::new(&mut *db.conn(), "foo", "0.0.1", "0.0.1", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", "0.0.1", "0.0.1", None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.owners, vec![("barfoo".into(), "https://example.org/barfoo".into())] @@ -1150,9 +1186,13 @@ mod tests { }) .create()?; - let details = CrateDetails::new(&mut *db.conn(), "foo", "0.0.1", "0.0.1", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = db.async_conn().await; + CrateDetails::new(&mut conn, "foo", "0.0.1", "0.0.1", None) + .await + .unwrap() + .unwrap() + }); assert_eq!( details.owners, vec![("barfoo".into(), "https://example.org/barfoov2".into())] @@ -1535,9 +1575,13 @@ mod tests { check_readme("/crate/dummy/0.3.0", "storage readme"); check_readme("/crate/dummy/0.4.0", "storage meread"); - let details = CrateDetails::new(&mut *env.db().conn(), "dummy", "0.5.0", "0.5.0", None) - .unwrap() - .unwrap(); + let details = env.runtime().block_on(async move { + let mut conn = env.async_db().await.async_conn().await; + CrateDetails::new(&mut conn, "dummy", "0.5.0", "0.5.0", None) + .await + .unwrap() + .unwrap() + }); assert!(matches!( env.runtime() .block_on(details.fetch_readme(&env.runtime().block_on(env.async_storage()))), diff --git a/src/web/features.rs b/src/web/features.rs index dbe8af1c8..08ed87dbd 100644 --- a/src/web/features.rs +++ b/src/web/features.rs @@ -2,15 +2,13 @@ use super::headers::CanonicalUrl; use super::MatchSemver; use crate::{ db::types::Feature, - db::Pool, impl_axum_webpage, - web::{cache::CachePolicy, error::AxumResult, match_version_axum, MetaData}, + web::{ + cache::CachePolicy, error::AxumResult, extractors::DbConnection, match_version, MetaData, + }, }; use anyhow::anyhow; -use axum::{ - extract::{Extension, Path}, - response::IntoResponse, -}; +use axum::{extract::Path, response::IntoResponse}; use serde::Serialize; use sqlx::Row as _; use std::collections::{HashMap, VecDeque}; @@ -38,10 +36,10 @@ impl_axum_webpage! { pub(crate) async fn build_features_handler( Path((name, req_version)): Path<(String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, ) -> AxumResult { let (version, version_or_latest, is_latest_url) = - match match_version_axum(&pool, &name, Some(&req_version)) + match match_version(&mut conn, &name, Some(&req_version)) .await? .exact_name_only()? { @@ -57,8 +55,6 @@ pub(crate) async fn build_features_handler( } }; - let mut conn = pool.get_async().await?; - let metadata = MetaData::from_crate(&mut conn, &name, &version, &version_or_latest).await?; let row = sqlx::query( diff --git a/src/web/mod.rs b/src/web/mod.rs index af7ccfe91..c0605cc53 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -3,7 +3,7 @@ pub mod page; use crate::utils::get_correct_docsrs_style_file; -use crate::utils::{report_error, spawn_blocking}; +use crate::utils::report_error; use anyhow::{anyhow, bail, Context as _, Result}; use axum_extra::middleware::option_layer; use serde_json::Value; @@ -30,7 +30,7 @@ mod source; mod statics; mod status; -use crate::{db::Pool, impl_axum_webpage, Context}; +use crate::{impl_axum_webpage, Context}; use anyhow::Error; use axum::{ extract::Extension, @@ -45,7 +45,6 @@ use chrono::{DateTime, Utc}; use error::AxumNope; use page::TemplateData; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; -use postgres::Client; use semver::{Version, VersionReq}; use serde::Serialize; use std::net::{IpAddr, Ipv4Addr}; @@ -127,29 +126,27 @@ impl MatchSemver { /// This function will also check for crates where dashes in the name (`-`) have been replaced with /// underscores (`_`) and vice-versa. The return value will indicate whether the crate name has /// been matched exactly, or if there has been a "correction" in the name that matched instead. -fn match_version( - conn: &mut Client, +async fn match_version( + conn: &mut sqlx::PgConnection, name: &str, input_version: Option<&str>, ) -> Result { let (crate_id, corrected_name) = { - let rows = conn - .query( - "SELECT id, name - FROM crates - WHERE normalize_crate_name(name) = normalize_crate_name($1)", - &[&name], - ) - .context("error fetching crate")?; - - let row = rows.get(0).ok_or(AxumNope::CrateNotFound)?; + let row = sqlx::query!( + "SELECT id, name + FROM crates + WHERE normalize_crate_name(name) = normalize_crate_name($1)", + name, + ) + .fetch_optional(&mut *conn) + .await + .context("error fetching crate")? + .ok_or(AxumNope::CrateNotFound)?; - let id: i32 = row.get(0); - let db_name = row.get(1); - if db_name != name { - (id, Some(db_name)) + if row.name != name { + (row.id, Some(row.name)) } else { - (id, None) + (row.id, None) } }; @@ -157,7 +154,8 @@ fn match_version( // skipping and reporting versions that are not semver valid. // `releases_for_crate` is already sorted, newest version first. let releases = crate_details::releases_for_crate(conn, crate_id) - .expect("error fetching releases for crate"); + .await + .context("error fetching releases for crate")?; if releases.is_empty() { return Err(AxumNope::CrateNotFound); @@ -236,26 +234,6 @@ fn match_version( Err(AxumNope::VersionNotFound) } -// temporary wrapper around `match_version` for axum handlers. -// -// FIXME: this can go when we fully migrated to axum / async in web -async fn match_version_axum( - pool: &Pool, - name: &str, - input_version: Option<&str>, -) -> Result { - spawn_blocking({ - let name = name.to_owned(); - let input_version = input_version.map(str::to_owned); - let pool = pool.clone(); - move || { - let mut conn = pool.get()?; - Ok(match_version(&mut conn, &name, input_version.as_deref())?) - } - }) - .await -} - async fn log_timeouts_to_sentry(req: AxumRequest, next: Next) -> AxumResponse { let uri = req.uri().clone(); @@ -594,16 +572,20 @@ mod test { use serde_json::json; use test_case::test_case; - fn release(version: &str, env: &TestEnvironment) -> i32 { - env.fake_release() + async fn release(version: &str, env: &TestEnvironment) -> i32 { + env.async_fake_release() + .await .name("foo") .version(version) - .create() + .create_async() + .await .unwrap() } - fn version(v: Option<&str>, db: &TestDatabase) -> Option { - let version = match_version(&mut db.conn(), "foo", v) + async fn version(v: Option<&str>, db: &TestDatabase) -> Option { + let mut conn = db.async_conn().await; + let version = match_version(&mut conn, "foo", v) + .await .ok()? .exact_name_only() .ok()? @@ -810,26 +792,26 @@ mod test { #[test] // https://github.com/rust-lang/docs.rs/issues/223 fn prereleases_are_not_considered_for_semver() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; let version = |v| version(v, db); - let release = |v| release(v, env); + let release = |v| release(v, &env); - release("0.3.1-pre"); + release("0.3.1-pre").await; for search in &["*", "newest", "latest"] { - assert_eq!(version(Some(search)), semver("0.3.1-pre")); + assert_eq!(version(Some(search)).await, semver("0.3.1-pre")); } - release("0.3.1-alpha"); - assert_eq!(version(Some("0.3.1-alpha")), exact("0.3.1-alpha")); + release("0.3.1-alpha").await; + assert_eq!(version(Some("0.3.1-alpha")).await, exact("0.3.1-alpha")); - release("0.3.0"); + release("0.3.0").await; let three = semver("0.3.0"); - assert_eq!(version(None), three); + assert_eq!(version(None).await, three); // same thing but with "*" - assert_eq!(version(Some("*")), three); + assert_eq!(version(Some("*")).await, three); // make sure exact matches still work - assert_eq!(version(Some("0.3.0")), exact("0.3.0")); + assert_eq!(version(Some("0.3.0")).await, exact("0.3.0")); Ok(()) }); @@ -838,7 +820,7 @@ mod test { #[test] fn platform_dropdown_not_shown_with_no_targets() { wrapper(|env| { - release("0.1.0", env); + env.runtime().block_on(release("0.1.0", env)); let web = env.frontend(); let text = web.get("/foo/0.1.0/foo").send()?.text()?; let platform = kuchikiki::parse_html() @@ -868,19 +850,24 @@ mod test { #[test] // https://github.com/rust-lang/docs.rs/issues/221 fn yanked_crates_are_not_considered() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; - let release_id = release("0.3.0", env); - let query = "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'"; + let release_id = release("0.3.0", &env).await; + + sqlx::query!( + "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'", + release_id + ) + .execute(&mut *db.async_conn().await) + .await?; - db.conn().query(query, &[&release_id]).unwrap(); - assert_eq!(version(None, db), None); - assert_eq!(version(Some("0.3"), db), None); + assert_eq!(version(None, db).await, None); + assert_eq!(version(Some("0.3"), db).await, None); - release("0.1.0+4.1", env); - assert_eq!(version(Some("0.1.0+4.1"), db), exact("0.1.0+4.1")); - assert_eq!(version(None, db), semver("0.1.0+4.1")); + release("0.1.0+4.1", &env).await; + assert_eq!(version(Some("0.1.0+4.1"), db).await, exact("0.1.0+4.1")); + assert_eq!(version(None, db).await, semver("0.1.0+4.1")); Ok(()) }); @@ -889,20 +876,23 @@ mod test { #[test] // https://github.com/rust-lang/docs.rs/issues/1682 fn prereleases_are_considered_when_others_dont_match() { - wrapper(|env| { - let db = env.db(); + async_wrapper(|env| async move { + let db = env.async_db().await; // normal release - release("1.0.0", env); + release("1.0.0", &env).await; // prereleases - release("2.0.0-alpha.1", env); - release("2.0.0-alpha.2", env); + release("2.0.0-alpha.1", &env).await; + release("2.0.0-alpha.2", &env).await; // STAR gives me the prod release - assert_eq!(version(Some("*"), db), exact("1.0.0")); + assert_eq!(version(Some("*"), db).await, exact("1.0.0")); // prerelease query gives me the latest prerelease - assert_eq!(version(Some(">=2.0.0-alpha"), db), exact("2.0.0-alpha.2")); + assert_eq!( + version(Some(">=2.0.0-alpha"), db).await, + exact("2.0.0-alpha.2") + ); Ok(()) }) @@ -911,17 +901,17 @@ mod test { #[test] // vaguely related to https://github.com/rust-lang/docs.rs/issues/395 fn metadata_has_no_effect() { - wrapper(|env| { - let db = env.db(); - - release("0.1.0+4.1", env); - release("0.1.1", env); - assert_eq!(version(None, db), semver("0.1.1")); - release("0.5.1+zstd.1.4.4", env); - assert_eq!(version(None, db), semver("0.5.1+zstd.1.4.4")); - assert_eq!(version(Some("0.5"), db), semver("0.5.1+zstd.1.4.4")); + async_wrapper(|env| async move { + let db = env.async_db().await; + + release("0.1.0+4.1", &env).await; + release("0.1.1", &env).await; + assert_eq!(version(None, db).await, semver("0.1.1")); + release("0.5.1+zstd.1.4.4", &env).await; + assert_eq!(version(None, db).await, semver("0.5.1+zstd.1.4.4")); + assert_eq!(version(Some("0.5"), db).await, semver("0.5.1+zstd.1.4.4")); assert_eq!( - version(Some("0.5.1+zstd.1.4.4"), db), + version(Some("0.5.1+zstd.1.4.4"), db).await, exact("0.5.1+zstd.1.4.4") ); @@ -1006,12 +996,10 @@ mod test { #[test] fn metadata_from_crate() { - wrapper(|env| { - release("0.1.0", env); - let metadata = env.runtime().block_on(async move { - let mut conn = env.async_db().await.async_conn().await; - MetaData::from_crate(&mut conn, "foo", "0.1.0", "latest").await - }); + async_wrapper(|env| async move { + release("0.1.0", &env).await; + let mut conn = env.async_db().await.async_conn().await; + let metadata = MetaData::from_crate(&mut conn, "foo", "0.1.0", "latest").await; assert_eq!( metadata.unwrap(), MetaData { @@ -1034,7 +1022,7 @@ mod test { #[test] fn test_tabindex_is_present_on_topbar_crate_search_input() { wrapper(|env| { - release("0.1.0", env); + env.runtime().block_on(release("0.1.0", env)); let web = env.frontend(); let text = web.get("/foo/0.1.0/foo").send()?.text()?; let tabindex = kuchikiki::parse_html() diff --git a/src/web/releases.rs b/src/web/releases.rs index 572cc7b35..119559e0a 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -10,7 +10,7 @@ use crate::{ axum_parse_uri_with_params, axum_redirect, encode_url_path, error::{AxumNope, AxumResult}, extractors::DbConnection, - match_version_axum, + match_version, }, BuildQueue, Config, InstanceMetrics, }; @@ -500,7 +500,6 @@ impl_axum_webpage! { pub(crate) async fn search_handler( mut conn: DbConnection, - Extension(pool): Extension, Extension(config): Extension>, Extension(metrics): Extension>, Query(mut params): Query>, @@ -536,7 +535,7 @@ pub(crate) async fn search_handler( // since we never pass a version into `match_version` here, we'll never get // `MatchVersion::Exact`, so the distinction between `Exact` and `Semver` doesn't // matter - if let Ok(matchver) = match_version_axum(&pool, krate, None).await { + if let Ok(matchver) = match_version(&mut conn, krate, None).await { params.remove("query"); queries.extend(params); let (version, _) = matchver.version.into_parts(); diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index 3f28fdeee..a9bf4fde0 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -4,7 +4,7 @@ use crate::{ db::Pool, repositories::RepositoryStatsUpdater, storage::rustdoc_archive_path, - utils::{self, spawn_blocking}, + utils, web::{ axum_cached_redirect, axum_parse_uri_with_params, cache::CachePolicy, @@ -12,8 +12,9 @@ use crate::{ csp::Csp, encode_url_path, error::{AxumNope, AxumResult}, + extractors::DbConnection, file::File, - match_version_axum, + match_version, metrics::RenderingTimesRecorder, page::TemplateData, MatchSemver, MetaData, @@ -92,7 +93,7 @@ pub(crate) async fn rustdoc_redirector_handler( Extension(metrics): Extension>, Extension(storage): Extension>, Extension(config): Extension>, - Extension(pool): Extension, + mut conn: DbConnection, Query(query_pairs): Query>, uri: Uri, ) -> AxumResult { @@ -160,7 +161,7 @@ pub(crate) async fn rustdoc_redirector_handler( // it doesn't matter if the version that was given was exact or not, since we're redirecting // anyway rendering_time.step("match version"); - let v = match_version_axum(&pool, &crate_name, params.version.as_deref()).await?; + let v = match_version(&mut conn, &crate_name, params.version.as_deref()).await?; trace!(?v, "matched version"); if let Some(new_name) = v.corrected_name { // `match_version` checked against -/_ typos, so if we have a name here we should @@ -174,16 +175,9 @@ pub(crate) async fn rustdoc_redirector_handler( if target.ends_with(".js") { // this URL is actually from a crate-internal path, serve it there instead rendering_time.step("serve JS for crate"); - let krate = spawn_blocking({ - let crate_name = crate_name.clone(); - let version = version.clone(); - move || { - let mut conn = pool.get()?; - CrateDetails::new(&mut *conn, &crate_name, &version, &version, None) - } - }) - .await? - .ok_or(AxumNope::ResourceNotFound)?; + let krate = CrateDetails::new(&mut conn, &crate_name, &version, &version, None) + .await? + .ok_or(AxumNope::ResourceNotFound)?; rendering_time.step("fetch from storage"); @@ -227,20 +221,20 @@ pub(crate) async fn rustdoc_redirector_handler( // get target name and whether it has docs // FIXME: This is a bit inefficient but allowing us to use less code in general rendering_time.step("fetch release doc status"); - let (target_name, has_docs): (String, bool) = spawn_blocking({ - move || { - let mut conn = pool.get()?; - let row = conn.query_one( - "SELECT target_name, rustdoc_status - FROM releases - WHERE releases.id = $1", - &[&id], - )?; + let (target_name, has_docs): (String, bool) = { + let row = sqlx::query!( + "SELECT + target_name, + rustdoc_status + FROM releases + WHERE releases.id = $1", + id, + ) + .fetch_one(&mut *conn) + .await?; - Ok((row.get(0), row.get(1))) - } - }) - .await?; + (row.target_name, row.rustdoc_status) + }; let mut target = params.target.as_deref(); if target == Some("index.html") || target == Some(&target_name) { @@ -406,13 +400,15 @@ pub(crate) async fn rustdoc_html_server_handler( trace!("match version"); rendering_time.step("match version"); + let mut conn = pool.get_async().await?; + // Check the database for releases with the requested version while doing the following: // * If no matching releases are found, return a 404 with the underlying error // Then: // * If both the name and the version are an exact match, return the version of the crate. // * If there is an exact match, but the requested crate name was corrected (dashes vs. underscores), redirect to the corrected name. // * If there is a semver (but not exact) match, redirect to the exact version. - let release_found = match_version_axum(&pool, ¶ms.name, Some(¶ms.version)).await?; + let release_found = match_version(&mut conn, ¶ms.name, Some(¶ms.version)).await?; trace!(?release_found, "found release"); let (version, version_or_latest, is_latest_url) = match release_found.version { @@ -448,21 +444,13 @@ pub(crate) async fn rustdoc_html_server_handler( // Get the crate's details from the database // NOTE: we know this crate must exist because we just checked it above (or else `match_version` is buggy) - let krate = spawn_blocking({ - let params = params.clone(); - let version = version.clone(); - let version_or_latest = version_or_latest.clone(); - move || { - let mut conn = pool.get()?; - CrateDetails::new( - &mut *conn, - ¶ms.name, - &version, - &version_or_latest, - Some(&updater), - ) - } - }) + let krate = CrateDetails::new( + &mut conn, + ¶ms.name, + &version, + &version_or_latest, + Some(&updater), + ) .await? .ok_or(AxumNope::ResourceNotFound)?; @@ -758,11 +746,11 @@ fn path_for_version( pub(crate) async fn target_redirect_handler( Path((name, version, req_path)): Path<(String, String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, Extension(storage): Extension>, Extension(updater): Extension>, ) -> AxumResult { - let release_found = match_version_axum(&pool, &name, Some(&version)).await?; + let release_found = match_version(&mut conn, &name, Some(&version)).await?; let (version, version_or_latest, is_latest_url) = match release_found.version { MatchSemver::Exact((version, _)) => (version.clone(), version, false), MatchSemver::Latest((version, _)) => (version, "latest".to_string(), true), @@ -770,21 +758,13 @@ pub(crate) async fn target_redirect_handler( MatchSemver::Semver(_) => return Err(AxumNope::VersionNotFound), }; - let crate_details = spawn_blocking({ - let name = name.clone(); - let version = version.clone(); - let version_or_latest = version_or_latest.clone(); - move || { - let mut conn = pool.get()?; - CrateDetails::new( - &mut *conn, - &name, - &version, - &version_or_latest, - Some(&updater), - ) - } - }) + let crate_details = CrateDetails::new( + &mut conn, + &name, + &version, + &version_or_latest, + Some(&updater), + ) .await? .ok_or(AxumNope::VersionNotFound)?; @@ -867,11 +847,11 @@ pub(crate) async fn badge_handler( pub(crate) async fn download_handler( Path((name, req_version)): Path<(String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, Extension(storage): Extension>, Extension(config): Extension>, ) -> AxumResult { - let (version, _) = match_version_axum(&pool, &name, Some(&req_version)) + let (version, _) = match_version(&mut conn, &name, Some(&req_version)) .await? .exact_name_only()? .into_parts(); diff --git a/src/web/source.rs b/src/web/source.rs index 03baf9b9d..be0378576 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -1,4 +1,4 @@ -use super::{error::AxumResult, match_version_axum}; +use super::{error::AxumResult, match_version}; use crate::{ db::Pool, impl_axum_webpage, @@ -207,7 +207,7 @@ pub(crate) async fn source_browser_handler( ) -> AxumResult { let mut conn = pool.get_async().await?; - let v = match_version_axum(&pool, &name, Some(&version)).await?; + let v = match_version(&mut conn, &name, Some(&version)).await?; if let Some(new_name) = &v.corrected_name { // `match_version` checked against -/_ typos, so if we have a name here we should diff --git a/src/web/status.rs b/src/web/status.rs index 039fe4d1f..d4b881fc7 100644 --- a/src/web/status.rs +++ b/src/web/status.rs @@ -1,7 +1,6 @@ use super::cache::CachePolicy; -use crate::{ - db::Pool, - web::{axum_redirect, error::AxumResult, match_version_axum, MatchSemver}, +use crate::web::{ + axum_redirect, error::AxumResult, extractors::DbConnection, match_version, MatchSemver, }; use axum::{ extract::{Extension, Path}, @@ -12,7 +11,7 @@ use axum::{ pub(crate) async fn status_handler( Path((name, req_version)): Path<(String, String)>, - Extension(pool): Extension, + mut conn: DbConnection, ) -> impl IntoResponse { ( Extension(CachePolicy::NoStoreMustRevalidate), @@ -20,7 +19,7 @@ pub(crate) async fn status_handler( // We use an async block to emulate a try block so that we can apply the above CORS header // and cache policy to both successful and failed responses async move { - let (version, id) = match match_version_axum(&pool, &name, Some(&req_version)) + let (version, id) = match match_version(&mut conn, &name, Some(&req_version)) .await? .exact_name_only()? { @@ -33,8 +32,6 @@ pub(crate) async fn status_handler( } }; - let mut conn = pool.get_async().await?; - let rustdoc_status: bool = sqlx::query_scalar!( "SELECT releases.rustdoc_status FROM releases