Skip to content

Commit 1386293

Browse files
authored
Merge pull request #1938 from wheels-dev/peter/throw-on-column-not-found
Add throwOnColumnNotFound config setting
2 parents cfe1bc3 + 3bfa6bb commit 1386293

File tree

4 files changed

+77
-24
lines changed

4 files changed

+77
-24
lines changed

vendor/wheels/events/init/orm.cfm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
application.$wheels.deletePluginDirectories = true;
3333
application.$wheels.loadIncompatiblePlugins = true;
3434
application.$wheels.automaticValidations = true;
35+
application.$wheels.throwOnColumnNotFound = true;
3536
application.$wheels.setUpdatedAtOnCreate = true;
3637
application.$wheels.useExpandedColumnAliases = false;
3738
application.$wheels.lowerCaseTableNames = false;

vendor/wheels/model/sql.cfc

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,20 @@ component {
279279
}
280280
}
281281
}
282-
if (application.wheels.showErrorInformation && !Len(local.toAdd)) {
283-
Throw(
284-
type = "Wheels.ColumnNotFound",
285-
message = "Wheels looked for the column mapped to the `#local.property#` property but couldn't find it in the database table.",
286-
extendedInfo = "Verify the `order` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
287-
);
282+
if (!Len(local.toAdd)) {
283+
if (application.wheels.throwOnColumnNotFound) {
284+
Throw(
285+
type = "Wheels.ColumnNotFound",
286+
message = "Wheels looked for the column mapped to the `#local.property#` property but couldn't find it in the database table.",
287+
extendedInfo = "Verify the `order` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
288+
);
289+
} else {
290+
writeLog(
291+
text = "ColumnNotFound: column mapped to `#local.property#` not found in database table (order clause). Set throwOnColumnNotFound=true to throw an exception.",
292+
type = "warning",
293+
file = "wheels_columnnotfound"
294+
);
295+
}
288296
}
289297
}
290298
}
@@ -532,12 +540,20 @@ component {
532540
Added an exception in case the column specified in the select or group argument does not exist in the database.
533541
This will only be in case when not using "table.column" or "column AS something" since in those cases Wheels passes through the select clause unchanged.
534542
*/
535-
if (application.wheels.showErrorInformation && !Len(local.toAppend) && arguments.clause == "select" && ListFindNoCase(local.addedPropertiesByModel[local.associationKey], local.iItem) EQ 0) {
536-
Throw(
537-
type = "Wheels.ColumnNotFound",
538-
message = "Wheels looked for the column mapped to the `#local.iItem#` property but couldn't find it in the database table.",
539-
extendedInfo = "Verify the `#arguments.clause#` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
540-
);
543+
if (!Len(local.toAppend) && arguments.clause == "select" && ListFindNoCase(local.addedPropertiesByModel[local.associationKey], local.iItem) EQ 0) {
544+
if (application.wheels.throwOnColumnNotFound) {
545+
Throw(
546+
type = "Wheels.ColumnNotFound",
547+
message = "Wheels looked for the column mapped to the `#local.iItem#` property but couldn't find it in the database table.",
548+
extendedInfo = "Verify the `#arguments.clause#` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
549+
);
550+
} else {
551+
writeLog(
552+
text = "ColumnNotFound: column mapped to `#local.iItem#` not found in database table (#arguments.clause# clause). Set throwOnColumnNotFound=true to throw an exception.",
553+
type = "warning",
554+
file = "wheels_columnnotfound"
555+
);
556+
}
541557
}
542558

543559
if (Len(local.toAppend)) {
@@ -767,12 +783,24 @@ component {
767783
}
768784
}
769785
}
770-
if (application.wheels.showErrorInformation && !StructKeyExists(local.param, "column")) {
771-
Throw(
772-
type = "Wheels.ColumnNotFound",
773-
message = "Wheels looked for the column mapped to the `#local.param.property#` property but couldn't find it in the database table.",
774-
extendedInfo = "Verify the `where` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
775-
);
786+
if (!StructKeyExists(local.param, "column")) {
787+
if (application.wheels.throwOnColumnNotFound) {
788+
Throw(
789+
type = "Wheels.ColumnNotFound",
790+
message = "Wheels looked for the column mapped to the `#local.param.property#` property but couldn't find it in the database table.",
791+
extendedInfo = "Verify the `where` argument and/or your property to column mappings done with the `property` method inside the model's `config` method to make sure everything is correct."
792+
);
793+
} else {
794+
writeLog(
795+
text = "ColumnNotFound: column mapped to `#local.param.property#` not found in database table (where clause). Set throwOnColumnNotFound=true to throw an exception.",
796+
type = "warning",
797+
file = "wheels_columnnotfound"
798+
);
799+
// Undo the ? replacement so where/params arrays stay in sync.
800+
// The raw column name passes through to the database as-is.
801+
local.where = Replace(local.where, Replace(local.element, local.elementDataPart, "?", "one"), local.element);
802+
continue;
803+
}
776804
}
777805
local.temp = ReFind(
778806
"^[a-zA-Z0-9-_\.]* ?#variables.wheels.class.RESQLOperators#",

vendor/wheels/tests/specs/model/raisedErrorsSpec.cfc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ component extends="wheels.WheelsTest" {
4545
g.model("user").findAll(select="id,email,firstname,lastname,createdat,foo")
4646
}).toThrow("Wheels.ColumnNotFound")
4747
})
48+
49+
it("skips invalid select column when throwOnColumnNotFound is false", () => {
50+
application.wheels.throwOnColumnNotFound = false;
51+
try {
52+
result = g.model("user").findAll(select="id,firstname,nonexistentcolumn");
53+
expect(result.recordcount).toBeGTE(0);
54+
expect(result.columnList).toInclude("id");
55+
expect(result.columnList).toInclude("firstname");
56+
} finally {
57+
application.wheels.throwOnColumnNotFound = true;
58+
}
59+
})
4860
})
4961
}
50-
}
62+
}

vendor/wheels/tests/specs/model/sqlSpec.cfc

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ component extends="wheels.WheelsTest" {
5252
expect(Right(result[2], 6)).toBe("NOT IN")
5353

5454
result = g.model("author").$whereClause(where = "lastName LIKE 'Djurner'")
55-
55+
5656
expect(Right(result[2], 4)).toBe("LIKE")
5757

5858
result = g.model("author").$whereClause(where = "lastName NOT LIKE 'Djurner'")
@@ -69,13 +69,13 @@ component extends="wheels.WheelsTest" {
6969
expect(datatypes).toHaveKey(result[4].datatype)
7070

7171
result = g.model("post").$whereClause(where = "averagerating NOT IN(3.6,3.2)")
72-
72+
7373
expect(arraylen(result)).toBeGTE(4)
7474
expect(result[4]).toBeStruct()
7575
expect(datatypes).toHaveKey(result[4].datatype)
7676

7777
result = g.model("post").$whereClause(where = "averagerating = 3.6")
78-
78+
7979
expect(arraylen(result)).toBeGTE(4)
8080
expect(result[4]).toBeStruct()
8181
expect(datatypes).toHaveKey(result[4].datatype)
@@ -108,7 +108,7 @@ component extends="wheels.WheelsTest" {
108108
g.model("user").findall(where="username = '#badparams.username#' AND password = '#badparams.password#'", parameterize=2)
109109
}).toThrow("Wheels.ParameterMismatch")
110110
})
111-
111+
112112
it("protects against SQL Injection with Parameterize and Pagination", () => {
113113
badparams = {username = "tonyp", password = "tonyp123' OR password!='tonyp123"}
114114

@@ -136,6 +136,18 @@ component extends="wheels.WheelsTest" {
136136
}).toThrow("Wheels.ColumnNotFound");
137137

138138
});
139+
140+
it( "skips invalid select column in CONCAT when throwOnColumnNotFound is false", function(){
141+
application.wheels.throwOnColumnNotFound = false;
142+
try {
143+
actual = g.model("user").findAll(where = "username='tonyp'", select = "id,username,nonexistentcolumn");
144+
expect( actual.recordcount ).toBeGTE(1);
145+
expect( actual.columnList ).toInclude("id");
146+
expect( actual.columnList ).toInclude("username");
147+
} finally {
148+
application.wheels.throwOnColumnNotFound = true;
149+
}
150+
});
139151
})
140152
}
141-
}
153+
}

0 commit comments

Comments
 (0)