DatabaseSchemaBlueprintIntegrationTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. namespace Illuminate\Tests\Database;
  3. use BadMethodCallException;
  4. use Illuminate\Container\Container;
  5. use Illuminate\Database\Capsule\Manager as DB;
  6. use Illuminate\Database\Schema\Blueprint;
  7. use Illuminate\Database\Schema\Grammars\MySqlGrammar;
  8. use Illuminate\Database\Schema\Grammars\PostgresGrammar;
  9. use Illuminate\Database\Schema\Grammars\SQLiteGrammar;
  10. use Illuminate\Database\Schema\Grammars\SqlServerGrammar;
  11. use Illuminate\Support\Facades\Facade;
  12. use PHPUnit\Framework\TestCase;
  13. class DatabaseSchemaBlueprintIntegrationTest extends TestCase
  14. {
  15. protected $db;
  16. /**
  17. * Bootstrap Eloquent.
  18. *
  19. * @return void
  20. */
  21. protected function setUp(): void
  22. {
  23. $this->db = $db = new DB;
  24. $db->addConnection([
  25. 'driver' => 'sqlite',
  26. 'database' => ':memory:',
  27. ]);
  28. $db->setAsGlobal();
  29. $container = new Container;
  30. $container->instance('db', $db->getDatabaseManager());
  31. Facade::setFacadeApplication($container);
  32. }
  33. protected function tearDown(): void
  34. {
  35. Facade::clearResolvedInstances();
  36. Facade::setFacadeApplication(null);
  37. }
  38. public function testRenamingAndChangingColumnsWork()
  39. {
  40. $this->db->connection()->getSchemaBuilder()->create('users', function ($table) {
  41. $table->string('name');
  42. $table->string('age');
  43. });
  44. $blueprint = new Blueprint('users', function ($table) {
  45. $table->renameColumn('name', 'first_name');
  46. $table->integer('age')->change();
  47. });
  48. $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar);
  49. // Expect one of the following two query sequences to be present...
  50. $expected = [
  51. [
  52. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  53. 'DROP TABLE users',
  54. 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE BINARY, age INTEGER NOT NULL)',
  55. 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users',
  56. 'DROP TABLE __temp__users',
  57. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  58. 'DROP TABLE users',
  59. 'CREATE TABLE users (age VARCHAR(255) NOT NULL COLLATE BINARY, first_name VARCHAR(255) NOT NULL)',
  60. 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users',
  61. 'DROP TABLE __temp__users',
  62. ],
  63. [
  64. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  65. 'DROP TABLE users',
  66. 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE BINARY, age INTEGER NOT NULL)',
  67. 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users',
  68. 'DROP TABLE __temp__users',
  69. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  70. 'DROP TABLE users',
  71. 'CREATE TABLE users (first_name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL COLLATE BINARY)',
  72. 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users',
  73. 'DROP TABLE __temp__users',
  74. ],
  75. [
  76. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  77. 'DROP TABLE users',
  78. 'CREATE TABLE users (name VARCHAR(255) NOT NULL COLLATE "BINARY", age INTEGER NOT NULL)',
  79. 'INSERT INTO users (name, age) SELECT name, age FROM __temp__users',
  80. 'DROP TABLE __temp__users',
  81. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name, age FROM users',
  82. 'DROP TABLE users',
  83. 'CREATE TABLE users (first_name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL COLLATE "BINARY")',
  84. 'INSERT INTO users (first_name, age) SELECT name, age FROM __temp__users',
  85. 'DROP TABLE __temp__users',
  86. ],
  87. ];
  88. $this->assertTrue(in_array($queries, $expected));
  89. }
  90. public function testChangingColumnWithCollationWorks()
  91. {
  92. $this->db->connection()->getSchemaBuilder()->create('users', function ($table) {
  93. $table->string('age');
  94. });
  95. $blueprint = new Blueprint('users', function ($table) {
  96. $table->integer('age')->collation('RTRIM')->change();
  97. });
  98. $blueprint2 = new Blueprint('users', function ($table) {
  99. $table->integer('age')->collation('NOCASE')->change();
  100. });
  101. $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar);
  102. $expected = [
  103. [
  104. 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users',
  105. 'DROP TABLE users',
  106. 'CREATE TABLE users (age INTEGER NOT NULL COLLATE RTRIM)',
  107. 'INSERT INTO users (age) SELECT age FROM __temp__users',
  108. 'DROP TABLE __temp__users',
  109. ],
  110. [
  111. 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users',
  112. 'DROP TABLE users',
  113. 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "RTRIM")',
  114. 'INSERT INTO users (age) SELECT age FROM __temp__users',
  115. 'DROP TABLE __temp__users',
  116. ],
  117. ];
  118. $this->assertContains($queries, $expected);
  119. $queries = $blueprint2->toSql($this->db->connection(), new SQLiteGrammar);
  120. $expected = [
  121. [
  122. 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users',
  123. 'DROP TABLE users',
  124. 'CREATE TABLE users (age INTEGER NOT NULL COLLATE NOCASE)',
  125. 'INSERT INTO users (age) SELECT age FROM __temp__users',
  126. 'DROP TABLE __temp__users',
  127. ],
  128. [
  129. 'CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users',
  130. 'DROP TABLE users',
  131. 'CREATE TABLE users (age INTEGER NOT NULL COLLATE "NOCASE")',
  132. 'INSERT INTO users (age) SELECT age FROM __temp__users',
  133. 'DROP TABLE __temp__users',
  134. ],
  135. ];
  136. $this->assertContains($queries, $expected);
  137. }
  138. public function testRenameIndexWorks()
  139. {
  140. $this->db->connection()->getSchemaBuilder()->create('users', function ($table) {
  141. $table->string('name');
  142. $table->string('age');
  143. });
  144. $this->db->connection()->getSchemaBuilder()->table('users', function ($table) {
  145. $table->index(['name'], 'index1');
  146. });
  147. $blueprint = new Blueprint('users', function ($table) {
  148. $table->renameIndex('index1', 'index2');
  149. });
  150. $queries = $blueprint->toSql($this->db->connection(), new SQLiteGrammar);
  151. $expected = [
  152. 'DROP INDEX index1',
  153. 'CREATE INDEX index2 ON users (name)',
  154. ];
  155. $this->assertEquals($expected, $queries);
  156. $queries = $blueprint->toSql($this->db->connection(), new SqlServerGrammar);
  157. $expected = [
  158. 'sp_rename N\'"users"."index1"\', "index2", N\'INDEX\'',
  159. ];
  160. $this->assertEquals($expected, $queries);
  161. $queries = $blueprint->toSql($this->db->connection(), new MySqlGrammar);
  162. $expected = [
  163. 'alter table `users` rename index `index1` to `index2`',
  164. ];
  165. $this->assertEquals($expected, $queries);
  166. $queries = $blueprint->toSql($this->db->connection(), new PostgresGrammar);
  167. $expected = [
  168. 'alter index "index1" rename to "index2"',
  169. ];
  170. $this->assertEquals($expected, $queries);
  171. }
  172. public function testAddUniqueIndexWithoutNameWorks()
  173. {
  174. $this->db->connection()->getSchemaBuilder()->create('users', function ($table) {
  175. $table->string('name')->nullable();
  176. });
  177. $blueprintMySql = new Blueprint('users', function ($table) {
  178. $table->string('name')->nullable()->unique()->change();
  179. });
  180. $queries = $blueprintMySql->toSql($this->db->connection(), new MySqlGrammar);
  181. $expected = [
  182. [
  183. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  184. 'DROP TABLE users',
  185. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE BINARY)',
  186. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  187. 'DROP TABLE __temp__users',
  188. 'alter table `users` add unique `users_name_unique`(`name`)',
  189. ],
  190. [
  191. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  192. 'DROP TABLE users',
  193. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE "BINARY")',
  194. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  195. 'DROP TABLE __temp__users',
  196. 'alter table `users` add unique `users_name_unique`(`name`)',
  197. ],
  198. ];
  199. $this->assertContains($queries, $expected);
  200. $blueprintPostgres = new Blueprint('users', function ($table) {
  201. $table->string('name')->nullable()->unique()->change();
  202. });
  203. $queries = $blueprintPostgres->toSql($this->db->connection(), new PostgresGrammar);
  204. $expected = [
  205. [
  206. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  207. 'DROP TABLE users',
  208. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE BINARY)',
  209. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  210. 'DROP TABLE __temp__users',
  211. 'alter table "users" add constraint "users_name_unique" unique ("name")',
  212. ],
  213. [
  214. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  215. 'DROP TABLE users',
  216. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE "BINARY")',
  217. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  218. 'DROP TABLE __temp__users',
  219. 'alter table "users" add constraint "users_name_unique" unique ("name")',
  220. ],
  221. ];
  222. $this->assertContains($queries, $expected);
  223. $blueprintSQLite = new Blueprint('users', function ($table) {
  224. $table->string('name')->nullable()->unique()->change();
  225. });
  226. $queries = $blueprintSQLite->toSql($this->db->connection(), new SQLiteGrammar);
  227. $expected = [
  228. [
  229. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  230. 'DROP TABLE users',
  231. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE BINARY)',
  232. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  233. 'DROP TABLE __temp__users',
  234. 'create unique index "users_name_unique" on "users" ("name")',
  235. ],
  236. [
  237. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  238. 'DROP TABLE users',
  239. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE "BINARY")',
  240. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  241. 'DROP TABLE __temp__users',
  242. 'create unique index "users_name_unique" on "users" ("name")',
  243. ],
  244. ];
  245. $this->assertContains($queries, $expected);
  246. $blueprintSqlServer = new Blueprint('users', function ($table) {
  247. $table->string('name')->nullable()->unique()->change();
  248. });
  249. $queries = $blueprintSqlServer->toSql($this->db->connection(), new SqlServerGrammar);
  250. $expected = [
  251. [
  252. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  253. 'DROP TABLE users',
  254. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE BINARY)',
  255. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  256. 'DROP TABLE __temp__users',
  257. 'create unique index "users_name_unique" on "users" ("name")',
  258. ],
  259. [
  260. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  261. 'DROP TABLE users',
  262. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE "BINARY")',
  263. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  264. 'DROP TABLE __temp__users',
  265. 'create unique index "users_name_unique" on "users" ("name")',
  266. ],
  267. ];
  268. $this->assertContains($queries, $expected);
  269. }
  270. public function testAddUniqueIndexWithNameWorks()
  271. {
  272. $this->db->connection()->getSchemaBuilder()->create('users', function ($table) {
  273. $table->string('name')->nullable();
  274. });
  275. $blueprintMySql = new Blueprint('users', function ($table) {
  276. $table->string('name')->nullable()->unique('index1')->change();
  277. });
  278. $queries = $blueprintMySql->toSql($this->db->connection(), new MySqlGrammar);
  279. $expected = [
  280. [
  281. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  282. 'DROP TABLE users',
  283. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE BINARY)',
  284. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  285. 'DROP TABLE __temp__users',
  286. 'alter table `users` add unique `index1`(`name`)',
  287. ],
  288. [
  289. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  290. 'DROP TABLE users',
  291. 'CREATE TABLE users (name VARCHAR(255) DEFAULT NULL COLLATE "BINARY")',
  292. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  293. 'DROP TABLE __temp__users',
  294. 'alter table `users` add unique `index1`(`name`)',
  295. ],
  296. ];
  297. $this->assertContains($queries, $expected);
  298. $blueprintPostgres = new Blueprint('users', function ($table) {
  299. $table->unsignedInteger('name')->nullable()->unique('index1')->change();
  300. });
  301. $queries = $blueprintPostgres->toSql($this->db->connection(), new PostgresGrammar);
  302. $expected = [
  303. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  304. 'DROP TABLE users',
  305. 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)',
  306. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  307. 'DROP TABLE __temp__users',
  308. 'alter table "users" add constraint "index1" unique ("name")',
  309. ];
  310. $this->assertEquals($expected, $queries);
  311. $blueprintSQLite = new Blueprint('users', function ($table) {
  312. $table->unsignedInteger('name')->nullable()->unique('index1')->change();
  313. });
  314. $queries = $blueprintSQLite->toSql($this->db->connection(), new SQLiteGrammar);
  315. $expected = [
  316. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  317. 'DROP TABLE users',
  318. 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)',
  319. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  320. 'DROP TABLE __temp__users',
  321. 'create unique index "index1" on "users" ("name")',
  322. ];
  323. $this->assertEquals($expected, $queries);
  324. $blueprintSqlServer = new Blueprint('users', function ($table) {
  325. $table->unsignedInteger('name')->nullable()->unique('index1')->change();
  326. });
  327. $queries = $blueprintSqlServer->toSql($this->db->connection(), new SqlServerGrammar);
  328. $expected = [
  329. 'CREATE TEMPORARY TABLE __temp__users AS SELECT name FROM users',
  330. 'DROP TABLE users',
  331. 'CREATE TABLE users (name INTEGER UNSIGNED DEFAULT NULL)',
  332. 'INSERT INTO users (name) SELECT name FROM __temp__users',
  333. 'DROP TABLE __temp__users',
  334. 'create unique index "index1" on "users" ("name")',
  335. ];
  336. $this->assertEquals($expected, $queries);
  337. }
  338. public function testItEnsuresDroppingMultipleColumnsIsAvailable()
  339. {
  340. $this->expectException(BadMethodCallException::class);
  341. $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification.");
  342. $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) {
  343. $table->dropColumn('name');
  344. $table->dropColumn('email');
  345. });
  346. }
  347. public function testItEnsuresRenamingMultipleColumnsIsAvailable()
  348. {
  349. $this->expectException(BadMethodCallException::class);
  350. $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification.");
  351. $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) {
  352. $table->renameColumn('name', 'first_name');
  353. $table->renameColumn('name2', 'last_name');
  354. });
  355. }
  356. public function testItEnsuresRenamingAndDroppingMultipleColumnsIsAvailable()
  357. {
  358. $this->expectException(BadMethodCallException::class);
  359. $this->expectExceptionMessage("SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification.");
  360. $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) {
  361. $table->dropColumn('name');
  362. $table->renameColumn('name2', 'last_name');
  363. });
  364. }
  365. public function testItEnsuresDroppingForeignKeyIsAvailable()
  366. {
  367. $this->expectException(BadMethodCallException::class);
  368. $this->expectExceptionMessage("SQLite doesn't support dropping foreign keys (you would need to re-create the table).");
  369. $this->db->connection()->getSchemaBuilder()->table('users', function (Blueprint $table) {
  370. $table->dropForeign('something');
  371. });
  372. }
  373. }