@@ -241,7 +241,140 @@ $migration->reset();
241241$migration->up();
242242```
243243
244- The Migration object controls the database version.
244+ The Migration object controls the database version.
245+
246+
247+ ### Tips on writing SQL migrations
248+
249+ #### Rely on explicit transactions
250+
251+ ``` sql
252+ -- DO
253+ BEGIN ;
254+
255+ ALTER TABLE 1 ;
256+ UPDATE 1 ;
257+ UPDATE 2 ;
258+ UPDATE 3 ;
259+ ALTER TABLE 2 ;
260+
261+ COMMIT ;
262+
263+
264+ -- DON'T
265+ ALTER TABLE 1 ;
266+ UPDATE 1 ;
267+ UPDATE 2 ;
268+ UPDATE 3 ;
269+ ALTER TABLE 2 ;
270+ ```
271+
272+ It is generally desirable to wrap migration scripts inside a ` BEGIN; ... COMMIT; ` block.
273+ This way, if _ any_ of the inner statements fail, _ none_ of them are committed and the
274+ database does not end up in an inconsistent state.
275+
276+ Mind that in case of a failure ` byjg/migration ` will always mark the migration as ` partial `
277+ and warn you when you attempt to run it again. The difference is that with explicit
278+ transactions you know that the database cannot be in an inconsistent state after an
279+ unexpected failure.
280+
281+ #### On creating triggers and SQL functions
282+
283+ ``` sql
284+ -- DO
285+ CREATE FUNCTION emp_stamp () RETURNS trigger AS $emp_stamp$
286+ BEGIN
287+ -- Check that empname and salary are given
288+ IF NEW .empname IS NULL THEN
289+ RAISE EXCEPTION ' empname cannot be null' ; -- it doesn't matter if these comments are blank or not
290+ END IF; --
291+ IF NEW .salary IS NULL THEN
292+ RAISE EXCEPTION ' % cannot have null salary' , NEW .empname ; --
293+ END IF; --
294+
295+ -- Who works for us when they must pay for it?
296+ IF NEW .salary < 0 THEN
297+ RAISE EXCEPTION ' % cannot have a negative salary' , NEW .empname ; --
298+ END IF; --
299+
300+ -- Remember who changed the payroll when
301+ NEW .last_date := current_timestamp ; --
302+ NEW .last_user := current_user ; --
303+ RETURN NEW; --
304+ END; --
305+ $emp_stamp$ LANGUAGE plpgsql;
306+
307+
308+ -- DON'T
309+ CREATE FUNCTION emp_stamp () RETURNS trigger AS $emp_stamp$
310+ BEGIN
311+ -- Check that empname and salary are given
312+ IF NEW .empname IS NULL THEN
313+ RAISE EXCEPTION ' empname cannot be null' ;
314+ END IF;
315+ IF NEW .salary IS NULL THEN
316+ RAISE EXCEPTION ' % cannot have null salary' , NEW .empname ;
317+ END IF;
318+
319+ -- Who works for us when they must pay for it?
320+ IF NEW .salary < 0 THEN
321+ RAISE EXCEPTION ' % cannot have a negative salary' , NEW .empname ;
322+ END IF;
323+
324+ -- Remember who changed the payroll when
325+ NEW .last_date := current_timestamp ;
326+ NEW .last_user := current_user ;
327+ RETURN NEW;
328+ END;
329+ $emp_stamp$ LANGUAGE plpgsql;
330+ ```
331+
332+ Since the ` PDO ` database abstraction layer cannot run batches of SQL statements,
333+ when ` byjg/migration ` reads a migration file it has to split up the whole contents of the SQL
334+ file at the semicolons, and run the statements one by one. However, there is one kind of
335+ statement that can have multiple semicolons in-between its body: functions.
336+
337+ In order to be able to parse functions correctly, ` byjg/migration ` 2.1.0 started splitting migration
338+ files at the ` semicolon + EOL ` sequence instead of just the semicolon. This way, if you append an empty
339+ comment after every inner semicolon of a function definition ` byjg/migration ` will be able to parse it.
340+
341+ Unfortunately, if you forget to add any of these comments the library will split the ` CREATE FUNCTION ` statement in
342+ multiple parts and the migration will fail.
343+
344+ #### Avoid the colon character (` : ` )
345+
346+ ``` sql
347+ -- DO
348+ CREATE TABLE bookings (
349+ booking_id UUID PRIMARY KEY ,
350+ booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE ) <= check_in),
351+ check_in DATE NOT NULL
352+ );
353+
354+
355+ -- DON'T
356+ CREATE TABLE bookings (
357+ booking_id UUID PRIMARY KEY ,
358+ booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
359+ check_in DATE NOT NULL
360+ );
361+ ```
362+
363+ Since ` PDO ` uses the colon character to prefix named parameters in prepared statements, its use will trip it
364+ up in other contexts.
365+
366+ For instance, PostgreSQL statements can use ` :: ` to cast values between types. On the other hand ` PDO ` will
367+ read this as an invalid named parameter in an invalid context and fail when it tries to run it.
368+
369+ The only way to fix this inconsistency is avoiding colons altogether (in this case, PostgreSQL also has an alternative
370+ syntax: ` CAST(value AS type) ` ).
371+
372+ #### Use an SQL editor
373+
374+ Finally, writing manual SQL migrations can be tiresome, but it is significantly easier if
375+ you use an editor capable of understanding the SQL syntax, providing autocomplete,
376+ introspecting your current database schema and/or autoformatting your code.
377+
245378
246379### Handle different migration inside one schema
247380
0 commit comments