Skip to content

Commit 858bfb4

Browse files
authored
Merge pull request #1937 from wheels-dev/peter/throw-on-column-not-found-main
Add throwOnColumnNotFound config setting
2 parents 466570f + 097ecf2 commit 858bfb4

File tree

4 files changed

+78
-25
lines changed

4 files changed

+78
-25
lines changed

core/src/wheels/events/onapplicationstart.cfc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ component {
321321
application.$wheels.deletePluginDirectories = true;
322322
application.$wheels.loadIncompatiblePlugins = true;
323323
application.$wheels.automaticValidations = true;
324+
application.$wheels.throwOnColumnNotFound = true;
324325
application.$wheels.setUpdatedAtOnCreate = true;
325326
application.$wheels.useExpandedColumnAliases = false;
326327
application.$wheels.lowerCaseTableNames = false;

core/src/wheels/model/sql.cfc

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,20 @@ component {
274274
}
275275
}
276276
}
277-
if (application.wheels.showErrorInformation && !Len(local.toAdd)) {
278-
Throw(
279-
type = "Wheels.ColumnNotFound",
280-
message = "Wheels looked for the column mapped to the `#local.property#` property but couldn't find it in the database table.",
281-
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."
282-
);
277+
if (!Len(local.toAdd)) {
278+
if (application.wheels.throwOnColumnNotFound) {
279+
Throw(
280+
type = "Wheels.ColumnNotFound",
281+
message = "Wheels looked for the column mapped to the `#local.property#` property but couldn't find it in the database table.",
282+
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."
283+
);
284+
} else {
285+
writeLog(
286+
text = "ColumnNotFound: column mapped to `#local.property#` not found in database table (order clause). Set throwOnColumnNotFound=true to throw an exception.",
287+
type = "warning",
288+
file = "wheels_columnnotfound"
289+
);
290+
}
283291
}
284292
}
285293
}
@@ -527,12 +535,20 @@ component {
527535
Added an exception in case the column specified in the select or group argument does not exist in the database.
528536
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.
529537
*/
530-
if (application.wheels.showErrorInformation && !Len(local.toAppend) && arguments.clause == "select" && ListFindNoCase(local.addedPropertiesByModel[local.associationKey], local.iItem) EQ 0) {
531-
Throw(
532-
type = "Wheels.ColumnNotFound",
533-
message = "Wheels looked for the column mapped to the `#local.iItem#` property but couldn't find it in the database table.",
534-
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."
535-
);
538+
if (!Len(local.toAppend) && arguments.clause == "select" && ListFindNoCase(local.addedPropertiesByModel[local.associationKey], local.iItem) EQ 0) {
539+
if (application.wheels.throwOnColumnNotFound) {
540+
Throw(
541+
type = "Wheels.ColumnNotFound",
542+
message = "Wheels looked for the column mapped to the `#local.iItem#` property but couldn't find it in the database table.",
543+
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."
544+
);
545+
} else {
546+
writeLog(
547+
text = "ColumnNotFound: column mapped to `#local.iItem#` not found in database table (#arguments.clause# clause). Set throwOnColumnNotFound=true to throw an exception.",
548+
type = "warning",
549+
file = "wheels_columnnotfound"
550+
);
551+
}
536552
}
537553

538554
if (Len(local.toAppend)) {
@@ -762,12 +778,24 @@ component {
762778
}
763779
}
764780
}
765-
if (application.wheels.showErrorInformation && !StructKeyExists(local.param, "column")) {
766-
Throw(
767-
type = "Wheels.ColumnNotFound",
768-
message = "Wheels looked for the column mapped to the `#local.param.property#` property but couldn't find it in the database table.",
769-
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."
770-
);
781+
if (!StructKeyExists(local.param, "column")) {
782+
if (application.wheels.throwOnColumnNotFound) {
783+
Throw(
784+
type = "Wheels.ColumnNotFound",
785+
message = "Wheels looked for the column mapped to the `#local.param.property#` property but couldn't find it in the database table.",
786+
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."
787+
);
788+
} else {
789+
writeLog(
790+
text = "ColumnNotFound: column mapped to `#local.param.property#` not found in database table (where clause). Set throwOnColumnNotFound=true to throw an exception.",
791+
type = "warning",
792+
file = "wheels_columnnotfound"
793+
);
794+
// Undo the ? replacement so where/params arrays stay in sync.
795+
// The raw column name passes through to the database as-is.
796+
local.where = Replace(local.where, Replace(local.element, local.elementDataPart, "?", "one"), local.element);
797+
continue;
798+
}
771799
}
772800
local.temp = ReFind(
773801
"^[a-zA-Z0-9-_\.]* ?#variables.wheels.class.RESQLOperators#",

core/src/wheels/tests_testbox/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.Testbox" {
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+
}

core/src/wheels/tests_testbox/specs/model/sqlSpec.cfc

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ component extends="wheels.Testbox" {
2323
expect(result).toHaveLength(3)
2424
expect(result[3].type).toBe("cf_sql_integer")
2525
expect(Right(result[2], Len(i))).toBe(i)
26-
26+
2727
result = g.model("author").$whereClause(where = "id #i#999")
2828

2929
expect(result[2]).toHaveLength(19+len(i))
@@ -43,7 +43,7 @@ component extends="wheels.Testbox" {
4343
expect(Right(result[2], 6)).toBe("NOT IN")
4444

4545
result = g.model("author").$whereClause(where = "lastName LIKE 'Djurner'")
46-
46+
4747
expect(Right(result[2], 4)).toBe("LIKE")
4848

4949
result = g.model("author").$whereClause(where = "lastName NOT LIKE 'Djurner'")
@@ -60,13 +60,13 @@ component extends="wheels.Testbox" {
6060
expect(datatypes).toHaveKey(result[4].datatype)
6161

6262
result = g.model("post").$whereClause(where = "averagerating NOT IN(3.6,3.2)")
63-
63+
6464
expect(arraylen(result)).toBeGTE(4)
6565
expect(result[4]).toBeStruct()
6666
expect(datatypes).toHaveKey(result[4].datatype)
6767

6868
result = g.model("post").$whereClause(where = "averagerating = 3.6")
69-
69+
7070
expect(arraylen(result)).toBeGTE(4)
7171
expect(result[4]).toBeStruct()
7272
expect(datatypes).toHaveKey(result[4].datatype)
@@ -99,7 +99,7 @@ component extends="wheels.Testbox" {
9999
g.model("user").findall(where="username = '#badparams.username#' AND password = '#badparams.password#'", parameterize=2)
100100
}).toThrow("Wheels.ParameterMismatch")
101101
})
102-
102+
103103
it("protects against SQL Injection with Parameterize and Pagination", () => {
104104
badparams = {username = "tonyp", password = "tonyp123' OR password!='tonyp123"}
105105

@@ -127,6 +127,18 @@ component extends="wheels.Testbox" {
127127
}).toThrow("Wheels.ColumnNotFound");
128128

129129
});
130+
131+
it( "skips invalid select column in CONCAT when throwOnColumnNotFound is false", function(){
132+
application.wheels.throwOnColumnNotFound = false;
133+
try {
134+
actual = g.model("user").findAll(where = "username='tonyp'", select = "id,username,nonexistentcolumn");
135+
expect( actual.recordcount ).toBeGTE(1);
136+
expect( actual.columnList ).toInclude("id");
137+
expect( actual.columnList ).toInclude("username");
138+
} finally {
139+
application.wheels.throwOnColumnNotFound = true;
140+
}
141+
});
130142
})
131143
}
132-
}
144+
}

0 commit comments

Comments
 (0)