| 113 | } |
| 114 | |
| 115 | async function dropExtras(t: SqlTarget, extras: string[]): Promise<string[]> { |
| 116 | if (extras.length === 0) { |
| 117 | return []; |
| 118 | } |
| 119 | const dropped: string[] = []; |
| 120 | |
| 121 | try { |
| 122 | switch (t.kind) { |
| 123 | case 'postgres': |
| 124 | case 'postgis': |
| 125 | await withPg(t.port, async c => { |
| 126 | for (const name of extras) { |
| 127 | const q = name.replace(/"/g, '""'); |
| 128 | try { |
| 129 | await c.query(`DROP DATABASE IF EXISTS "${q}" WITH (FORCE)`); |
| 130 | dropped.push(name); |
| 131 | } catch { |
| 132 | /* skip */ |
| 133 | } |
| 134 | } |
| 135 | }); |
| 136 | break; |
| 137 | case 'mysql': |
| 138 | case 'mariadb': |
| 139 | await withMysql(t.port, async c => { |
| 140 | await c.query('SET FOREIGN_KEY_CHECKS=0'); |
| 141 | for (const name of extras) { |
| 142 | const q = name.replace(/`/g, '``'); |
| 143 | try { |
| 144 | await c.query(`DROP DATABASE IF EXISTS \`${q}\``); |
| 145 | dropped.push(name); |
| 146 | } catch { |
| 147 | /* skip */ |
| 148 | } |
| 149 | } |
| 150 | }); |
| 151 | break; |
| 152 | case 'mssql': |
| 153 | await withMssql(t.port, async c => { |
| 154 | for (const name of extras) { |
| 155 | const q = name.replace(/]/g, ']]'); |
| 156 | try { |
| 157 | // `set offline` over `set single_user` to avoid the 3702 race where a torn-down |
| 158 | // pool connection grabs the single-user slot before the drop executes. Dropping |
| 159 | // an offline database leaves the underlying `.mdf`/`.ldf` files behind, so |
| 160 | // capture the physical paths first and explicitly remove them after the drop — |
| 161 | // otherwise a subsequent run that creates the same DB name fails with error |
| 162 | // 5170 ("file already exists"). |
| 163 | await mssqlQuery( |
| 164 | c, |
| 165 | `declare @drop_files table (path nvarchar(260)); ` + |
| 166 | `insert into @drop_files (path) select physical_name from sys.master_files where database_id = db_id(N'${q}'); ` + |
| 167 | `alter database [${q}] set offline with rollback immediate; ` + |
| 168 | `drop database [${q}]; ` + |
| 169 | `declare @drop_path nvarchar(260); ` + |
| 170 | `declare drop_files_cursor cursor local fast_forward for select path from @drop_files; ` + |
| 171 | `open drop_files_cursor; fetch next from drop_files_cursor into @drop_path; ` + |
| 172 | `while @@fetch_status = 0 begin ` + |