Repository navigation
docs(api): validate Migrator section #2463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
23 changes: 22 additions & 1 deletion
23
vendor/wheels/public/docs/reference/migration/addcolumn.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,22 @@ | ||
| addColumn(table='members', columnType='string', columnName='status', limit=50); | ||
| // 1. Add a simple string column to an existing table | ||
| addColumn(table="members", columnType="string", columnName="status", limit=50); | ||
|
|
||
| // 2. Add a boolean column with a default value and no NULLs allowed | ||
| addColumn( | ||
| table="members", | ||
| columnType="boolean", | ||
| columnName="isActive", | ||
| default=1, | ||
| allowNull=false | ||
| ); | ||
|
|
||
| // 3. Add a decimal column with precision and scale (e.g. for a price field) | ||
| addColumn( | ||
| table="products", | ||
| columnType="decimal", | ||
| columnName="price", | ||
| precision=10, | ||
| scale=2, | ||
| default=0, | ||
| allowNull=false | ||
| ); |
31 changes: 31 additions & 0 deletions
31
vendor/wheels/public/docs/reference/migration/addforeignkey.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // 1. Add a foreign key from orders.customerId to customers.id | ||
| addForeignKey( | ||
| table="orders", | ||
| referenceTable="customers", | ||
| column="customerId", | ||
| referenceColumn="id" | ||
| ); | ||
|
|
||
| // 2. Add a foreign key from comments.postId to posts.id | ||
| addForeignKey( | ||
| table="comments", | ||
| referenceTable="posts", | ||
| column="postId", | ||
| referenceColumn="id" | ||
| ); | ||
|
|
||
| // 3. Use addForeignKey in a migration's up() and remove it in down() | ||
| // In your migration CFC: | ||
| // | ||
| // public void function up() { | ||
| // addForeignKey( | ||
| // table="order_items", | ||
| // referenceTable="orders", | ||
| // column="orderId", | ||
| // referenceColumn="id" | ||
| // ); | ||
| // } | ||
| // | ||
| // public void function down() { | ||
| // dropForeignKey(table="order_items", keyName="FK_order_items_orders"); | ||
| // } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,12 @@ | ||
| addIndex(table='members', columnNames='username', unique=true); | ||
| // 1. Add a basic index on a single column | ||
| addIndex(table="users", columnNames="email"); | ||
|
|
||
| // 2. Add a unique index to enforce uniqueness on a column | ||
| addIndex(table="members", columnNames="username", unique=true); | ||
|
|
||
| // 3. Add a composite index on multiple columns | ||
| addIndex(table="orders", columnNames="customerId,createdAt"); | ||
|
|
||
| // 4. Add an index with a custom index name | ||
| // (defaults to tableName_firstColumnName, e.g. "posts_publishedAt") | ||
| addIndex(table="posts", columnNames="publishedAt", indexName="idx_posts_published"); |
38 changes: 31 additions & 7 deletions
38
vendor/wheels/public/docs/reference/migration/addrecord.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,32 @@ | ||
| addRecord(table='people', | ||
| id = 1, | ||
| title = "Mr", | ||
| firstname = "Bruce", | ||
| lastname = "Wayne", | ||
| email = "bruce@wayneenterprises.com", | ||
| tel = "555-67869099", | ||
| // 1. Insert a simple seed record into a settings table | ||
| addRecord( | ||
| table = "settings", | ||
| name = "siteName", | ||
| value = "My Wheels App" | ||
| ); | ||
|
|
||
| // 2. Insert a record with multiple columns (extra keyword arguments become column/value pairs) | ||
| addRecord( | ||
| table = "people", | ||
| id = 1, | ||
| title = "Mr", | ||
| firstName = "Bruce", | ||
| lastName = "Wayne", | ||
| email = "bruce@wayneenterprises.com", | ||
| phone = "555-678-9099" | ||
| ); | ||
|
|
||
| // 3. Seed an admin user role during a migration's up() function | ||
| component extends="wheels.migrator.Migration" { | ||
| function up() { | ||
| addRecord( | ||
| table = "roles", | ||
| id = 1, | ||
| name = "admin", | ||
| active = true | ||
| ); | ||
| } | ||
| function down() { | ||
| removeRecord(table = "roles", where = "id = 1"); | ||
| } | ||
| } |
17 changes: 17 additions & 0 deletions
17
vendor/wheels/public/docs/reference/migration/addreference.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // 1. Add a foreign key from comments.postId to posts.id using a reference name | ||
| // Equivalent to: addForeignKey(table="comments", referenceTable="posts", column="postId", referenceColumn="id") | ||
| addReference(table="comments", referenceName="post"); | ||
|
|
||
| // 2. Add a foreign key from order_items.orderId to orders.id | ||
| addReference(table="order_items", referenceName="order"); | ||
|
|
||
| // 3. Use addReference in a migration's up() and undo it with dropReference() in down() | ||
| // In your migration CFC: | ||
| // | ||
| // public void function up() { | ||
| // addReference(table="comments", referenceName="post"); | ||
| // } | ||
| // | ||
| // public void function down() { | ||
| // dropReference(table="comments", referenceName="post"); | ||
| // } |
44 changes: 44 additions & 0 deletions
44
vendor/wheels/public/docs/reference/migration/announce.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // 1. Log a custom message during a migration's up() step | ||
| function up() { | ||
| transaction { | ||
| try { | ||
| createTable(name="articles", force=true) { | ||
| t.string(columnNames="title", null=false); | ||
| t.text("body"); | ||
| t.timestamps(); | ||
| }; | ||
| announce("Created articles table"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (StructKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw(errorCode="1", detail=local.exception.detail, message=local.exception.message, type="any"); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. Announce multiple steps to provide granular feedback | ||
| function up() { | ||
| transaction { | ||
| try { | ||
| addColumn(table="users", columnType="string", columnName="apiKey", limit=64, null=true); | ||
| announce("Added apiKey column to users"); | ||
|
|
||
| addIndex(table="users", columnNames="apiKey", unique=true); | ||
| announce("Added unique index on users.apiKey"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (StructKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw(errorCode="1", detail=local.exception.detail, message=local.exception.message, type="any"); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
12 changes: 11 additions & 1 deletion
12
vendor/wheels/public/docs/reference/migration/changecolumn.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,11 @@ | ||
| changeColumn(table='members', columnType='string', columnName='status', limit=50); | ||
| // 1. Change a string column's length limit | ||
| changeColumn(table="members", columnName="status", columnType="string", limit=50); | ||
|
|
||
| // 2. Change a column type and set a default value | ||
| changeColumn(table="orders", columnName="quantity", columnType="integer", default=1); | ||
|
|
||
| // 3. Change a decimal column with precision and scale | ||
| changeColumn(table="products", columnName="price", columnType="decimal", precision=10, scale=2, allowNull=false); | ||
|
|
||
| // 4. Change a text column and explicitly allow NULL values | ||
| changeColumn(table="articles", columnName="summary", columnType="text", allowNull=true); |
16 changes: 14 additions & 2 deletions
16
vendor/wheels/public/docs/reference/migration/changetable.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,15 @@ | ||
| t = changeTable(name='employees'); | ||
| t.string(columnNames="fullName", default="", allowNull=true, limit="255"); | ||
| // 1. Add a new string column to an existing table | ||
| t = changeTable(name="employees"); | ||
| t.string(columnNames="fullName", default="", allowNull=true, limit=255); | ||
| t.change(); | ||
|
|
||
| // 2. Modify multiple columns at once (change type and nullability) | ||
| t = changeTable(name="products"); | ||
| t.integer(columnNames="stock", default=0, allowNull=false); | ||
| t.boolean(columnNames="active", default=true, allowNull=false); | ||
| t.change(); | ||
|
|
||
| // 3. Add a new column using addColumns=true so the migration fails gracefully if the column already exists | ||
| t = changeTable(name="orders"); | ||
| t.datetime(columnNames="shippedAt", allowNull=true); | ||
| t.change(addColumns=true); |
32 changes: 15 additions & 17 deletions
32
vendor/wheels/public/docs/reference/migration/createtable.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,23 @@ | ||
| // Example: create a users table | ||
| t = createTable(name='users'); | ||
| t.string(columnNames='firstname,lastname', default='', allowNull=false, limit=50); | ||
| t.string(columnNames='email', default='', allowNull=false, limit=255); | ||
| t.string(columnNames='passwordHash', default='', allowNull=true, limit=500); | ||
| t.string(columnNames='passwordResetToken,verificationToken', default='', allowNull=true, limit=500); | ||
| t.boolean(columnNames='passwordChangeRequired,verified', default=false); | ||
| t.datetime(columnNames='passwordResetTokenAt,passwordResetAt,loggedinAt', default='', allowNull=true); | ||
| t.integer(columnNames='roleid', default=0, allowNull=false, limit=3); | ||
| // 1. Create a users table with standard columns and timestamps | ||
| t = createTable(name="users"); | ||
| t.string(columnNames="firstName,lastName", default="", allowNull=false, limit=50); | ||
| t.string(columnNames="email", default="", allowNull=false, limit=255); | ||
| t.string(columnNames="passwordHash", default="", allowNull=true, limit=500); | ||
| t.boolean(columnNames="verified", default=false); | ||
| t.integer(columnNames="roleId", default=0, allowNull=false); | ||
| t.timestamps(); | ||
| t.create(); | ||
|
|
||
| // Example: Create a table with a different Primary Key | ||
| t = createTable(name='tokens', id=false); | ||
| t.primaryKey(name='id', allowNull=false, type="string", limit=35 ); | ||
| // 2. Create a table with a custom string primary key (disabling the auto integer id) | ||
| t = createTable(name="tokens", id=false); | ||
| t.primaryKey(name="id", allowNull=false, type="string", limit=36); | ||
| t.string(columnNames="userId", allowNull=false, limit=36); | ||
| t.datetime(columnNames="expiresAt", allowNull=false); | ||
| t.integer(columnNames='requests', default=0, allowNull=false); | ||
| t.timestamps(); | ||
| t.create(); | ||
|
|
||
| // Example: Create a Join Table with composite primary keys | ||
| t = createTable(name='userkintins', id=false); | ||
| t.primaryKey(name="userid", allowNull=false, limit=11); | ||
| t.primaryKey(name='profileid', type="string", limit=11 ); | ||
| // 3. Create a join table with composite primary keys and force=true to recreate if it already exists | ||
| t = createTable(name="userRoles", id=false, force=true); | ||
| t.primaryKey(name="userId", allowNull=false, limit=11); | ||
| t.primaryKey(name="roleId", allowNull=false, limit=11); | ||
| t.create(); |
21 changes: 21 additions & 0 deletions
21
vendor/wheels/public/docs/reference/migration/createview.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // 1. Create a simple database view that joins users and roles | ||
| v = createView(name="userRoles"); | ||
| v.selectStatement(sql="SELECT u.id, u.firstName, u.lastName, r.name AS roleName FROM users u INNER JOIN roles r ON u.roleId = r.id"); | ||
| v.create(); | ||
|
|
||
| // 2. Create a view for active (non-deleted) users using method chaining | ||
| createView(name="activeUsers") | ||
| .selectStatement(sql="SELECT id, firstName, lastName, email FROM users WHERE deletedAt IS NULL") | ||
| .create(); | ||
|
|
||
| // 3. Full up/down migration using createView and dropView | ||
| component extends="wheels.migrator.Migration" { | ||
| function up() { | ||
| createView(name="publishedArticles") | ||
| .selectStatement(sql="SELECT id, title, body, authorId, publishedAt FROM articles WHERE publishedAt IS NOT NULL") | ||
| .create(); | ||
| } | ||
| function down() { | ||
| dropView(name="publishedArticles"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,95 @@ | ||
| // 1. Reverse a table creation by dropping the table | ||
| // Called automatically when rolling back this migration | ||
| function down() { | ||
| transaction { | ||
| try { | ||
| // your code goes here | ||
| dropTable('myTable'); | ||
| dropTable("employees"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (StructKeyExists(local, "exception")) { | ||
| if (structKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw(errorCode="1", detail=local.exception.detail, message=local.exception.message, type="any"); | ||
| throw( | ||
| errorCode = "1", | ||
| detail = local.exception.detail, | ||
| message = local.exception.message, | ||
| type = "any" | ||
| ); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. Reverse an addColumn() call by removing the column | ||
| function down() { | ||
| transaction { | ||
| try { | ||
| removeColumn(table="users", columnName="biography"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (structKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw( | ||
| errorCode = "1", | ||
| detail = local.exception.detail, | ||
| message = local.exception.message, | ||
| type = "any" | ||
| ); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 3. Paired up() and down() inside a full migration component | ||
| component extends="[extends]" hint="Add status column to orders" { | ||
|
|
||
| function up() { | ||
| transaction { | ||
| try { | ||
| addColumn(table="orders", columnType="string", columnName="status", limit=50, default="pending"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (structKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw( | ||
| errorCode = "1", | ||
| detail = local.exception.detail, | ||
| message = local.exception.message, | ||
| type = "any" | ||
| ); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function down() { | ||
| transaction { | ||
| try { | ||
| removeColumn(table="orders", columnName="status"); | ||
| } catch (any e) { | ||
| local.exception = e; | ||
| } | ||
|
|
||
| if (structKeyExists(local, "exception")) { | ||
| transaction action="rollback"; | ||
| throw( | ||
| errorCode = "1", | ||
| detail = local.exception.detail, | ||
| message = local.exception.message, | ||
| type = "any" | ||
| ); | ||
| } else { | ||
| transaction action="commit"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } |
13 changes: 13 additions & 0 deletions
13
vendor/wheels/public/docs/reference/migration/dropforeignkey.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // 1. Drop a foreign key constraint by its explicit name | ||
| // In the down() function, remove a foreign key added in up() | ||
| dropForeignKey(table="orders", keyName="FK_orders_customers"); | ||
|
|
||
| // 2. Drop a foreign key that was created via addReference() | ||
| // addReference() creates keys named FK_<table>_<pluralizedReference> | ||
| // So addReference(table="comments", referenceName="post") creates "FK_comments_posts" | ||
| dropForeignKey(table="comments", keyName="FK_comments_posts"); | ||
|
|
||
| // 3. Use inside a migration's down() to reverse an addForeignKey() call | ||
| // up() called: addForeignKey(table="profiles", referenceTable="users", column="userId", referenceColumn="id") | ||
| // down() reverses it: | ||
| dropForeignKey(table="profiles", keyName="FK_profiles_users"); |
18 changes: 18 additions & 0 deletions
18
vendor/wheels/public/docs/reference/migration/dropreference.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // 1. Drop the foreign key from comments.postId back to posts.id | ||
| // Removes the constraint named FK_comments_posts | ||
| dropReference(table="comments", referenceName="post"); | ||
|
|
||
| // 2. Drop a foreign key from order_items back to orders | ||
| // Removes the constraint named FK_order_items_orders | ||
| dropReference(table="order_items", referenceName="order"); | ||
|
|
||
| // 3. Use dropReference in the down() of a migration that added a reference in up() | ||
| // In your migration CFC: | ||
| // | ||
| // public void function up() { | ||
| // addReference(table="comments", referenceName="post"); | ||
| // } | ||
| // | ||
| // public void function down() { | ||
| // dropReference(table="comments", referenceName="post"); | ||
| // } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 In
announce.txt, two examples passnull=false(line 6) andnull=true(line 28) — but neitherTableDefinition.string()noraddColumn()has anullparameter; the correct argument name isallowNull. CFML silently accepts unknown named arguments, so users copy/pasting these examples will get column definitions with the default nullability instead of the intended constraint, and every other example added in this PR consistently usesallowNull. Replacenull=falsewithallowNull=falseon line 6 andnull=truewithallowNull=trueon line 28.Extended reasoning...
What the bug is
The two examples in the new
announce.txtfile pass an unrecognizednullkeyword argument:t.string(columnNames="title", null=false);addColumn(table="users", columnType="string", columnName="apiKey", limit=64, null=true);Both call sites are documenting how to constrain (or relax) NULLability on a column, but the actual parameter is named
allowNull, notnull.Why this is wrong
I verified both function signatures directly:
vendor/wheels/migrator/TableDefinition.cfc:258—public any function string(string columnNames, any limit, string default, boolean allowNull)vendor/wheels/migrator/Migration.cfc:164-174—public void function addColumn(required string table, required string columnType, required string columnName = "", string afterColumn = "", string referenceName = "", string default, boolean allowNull, numeric limit, numeric precision, numeric scale)Neither function declares a
nullparameter. CFML does not error on unknown named arguments — it accepts them into theargumentsstruct and the called function simply never reads them. So the column ends up created with whatever the underlying default nullability is, not what the example reads as if it were specifying.Step-by-step proof
announce.txt:addColumn(table="users", columnType="string", columnName="apiKey", limit=64, null=true);addColumnwith arguments struct:{table:"users", columnType:"string", columnName:"apiKey", limit:64, null:true}.addColumn(Migration.cfc:164) setsarguments.addColumns = trueand forwards tochangeColumn(argumentCollection=arguments).changeColumnreads named parameters by their declared names —allowNull,default,limit, etc. Thenullkey in the struct is never read.NULLallowed). The user reads "null=true" and assumes they explicitly opted in to NULLs, but in reality the argument was silently dropped — there was never any control flow that examinedarguments.null.Why other examples don't hit this
Every other example added in this PR —
addcolumn.txt,biginteger.txt,boolean.txt,changecolumn.txt,createtable.txt,string.txt,text.txt, etc. — usesallowNull=…. Theannounce.txtexamples are the only inconsistency.Impact
Reference docs are copy-paste templates. A
null=falsein an example reads as "this column will be NOT NULL," but the produced DDL will have the database default. For a migration that is supposed to enforce a NOT NULL constraint onarticles.title(example #1), this is a real correctness drift — silently passing tests in dev (where data happens to be present) but failing the moment a row is inserted without the column.Fix
In
vendor/wheels/public/docs/reference/migration/announce.txt:null=falsetoallowNull=falsenull=truetoallowNull=true