Repository navigation
Escape SQL column names that conflict with table class members - #3875
Conversation
Tables defined in SQL are generated as classes that extend `Table` and
mix in `TableInfo` (or `ResultSet` with the drift3 preview), with a
field for each column. A column whose derived Dart name matched one of
their members, like `text` (`Table.text()`), `table_name`
(`Table.tableName`) or `map` (`TableInfo.map`), generated code that
failed to compile ("Can't declare a member that conflicts with an
inherited one").
The drift table resolver now appends a `$` to such names, as it already
does for reserved keywords. Names given with `AS` are not changed, and
since only members of the generated class are reserved, columns that
compiled before keep their names.
This also applies to tables reconstructed from SQL, e.g. for
`schema dump` on a database file or for schema snapshots with
`fixed_sql`. That analysis doesn't use the project's options, so those
tables are checked against the drift 2 classes even when the drift3
preview is enabled.
Fixes #2100
| /// members (e.g. `text`, which is a method on `Table`) would conflict with that | ||
| /// member. Passing these names as `reservedNames` to [dartNameForSqlColumn] | ||
| /// avoids that. | ||
| Set<String> tableClassMemberNames({ |
There was a problem hiding this comment.
Instead of creating a fresh set for every column, could we make these a constant? E.g. use
const alwaysForbidden = {'hashCode', ...};
return drift3 ? const {...alwaysForbidden, 'col', ...} : const {...alwaysForbidden, 'sqliteAny', ...};to avoid the 4x blowup, I think we can get away with unconditionally escaping moduleAndArgs.
There was a problem hiding this comment.
Done in 7453e0f, both sets are constants now. moduleAndArgs is reserved for every table, so the only visible change is that a module_and_args column in a regular table now gets moduleAndArgs$.
Fixed in b622b36, thanks!
I think that's acceptable, it'll get fixed once drift3 is released and the only option. |
`moduleAndArgs` is now reserved for all tables instead of only virtual ones, so one set per drift version is enough and both can be constants.
|
You're welcome, and thanks for fixing |
Co-authored-by: Simon Binder <oss@simonbinder.eu>
Fixes #2100
Tables defined in SQL are generated as classes that extend
Tableand mix inTableInfo(orResultSetwithdrift3_preview), with a field for each column. This applies to.driftfiles and to the tables drift reconstructs from SQL, e.g. forschema dumpon a database file or for schema snapshots that containfixed_sql. The Dart name of a column was derived from its SQL name without looking at the members of those classes, so a column liketext,integer,table_name,primary_key,maporhash_codegenerated code that doesn't compile:Fix
Like #3838 did for reserved keywords, this appends a
$to such names.dartNameForSqlColumngets an optionalreservedNamesparameter, and the drift table resolver passes the names of the members of the generated table class: those ofObject,TableandTableInfo(TableandResultSetfor drift3), plusmoduleAndArgsfor virtual tables.CREATE TABLE foo (text TEXT)now generates atext$column getter, while the SQL name and the default JSON key staytext.Since every reserved name is a member of the generated class, a column with one of these names never compiled before, so columns that work today keep their names (with one drift3 exception in the schema tools, see the end). For example,
aliasandcolumnsare only reserved with drift3, whose table classes have members with those names. Names set explicitly withASare left alone, and views and query result classes (which don't extendTable) are not affected.I also updated the sentence in the drift files docs that suggested
ASfor this case, and added a changelog entry.Tests
test/analysis/resolver/drift/table_test.dart: checks the derived names for drift 2 (including an fts5 table with amodule_and_argscolumn) and for drift3, including names that are kept because they aren't members (aliasfor drift 2,mapfor drift3).test/services/schema/writer_test.dart: for drift 2 and drift3, reads the members ofTableandTableInfo/ResultSetfrompackage:driftwith the analyzer, defines a table with one column per member and compiles the generated table class in the schema isolate (createSchemaJson(throwOnSchemaIsolateFailure: true)). Ondevelopthis fails with'attachedDatabase' is already declared in this scope(drift 2) and'alias' is already declared in this scope(drift3). Because the names come from the analyzer, the test will also fail if a member is added to these classes without updating the list.In
drift_dev,dart format -o none --set-exit-if-changed .,dart analyze --fatal-infos --fatal-warningsanddart testpass (512 passed, 1 skipped; I ran the tests with--timeout 4xbecause the machine was heavily loaded).I also ran a full
build_runnerbuild of a throwaway.driftfile with such columns and an fts5 table, generating data classes, companions and the manager. Ondevelop,dart analyzereports errors such asClass 'Foo' can't define field 'text' and have method 'Table.text' with the same namein the generated file. With this change it reports no issues, and inserting and querying a row through the generated API works. With the drift3 preview enabled, the generated code for these columns analyzes cleanly as well.Not covered here, so I left these out of this change:
mapstill breaks thetoColumnsmethod of the generated data class, which declares a local variable calledmap. That isn't related to inherited members.schema dumpon a database file, snapshots withfixed_sql) with default options rather than the project's, so with drift3 those tables are checked against the drift 2 members. A column namedaliasstill conflicts there, and names that are only drift 2 members (likemaporattached_database) get a$. Fixing that needs the project options passed into that analysis.