Compiler errors¶
In the previous chapters we made mistakes on purpose more than once and looked at what the compiler said. This chapter collects those errors. You do not have to read it in order: it is more convenient to come back to it when the compiler gives you an unclear message and find a similar one here.
First, how to read such messages. A GHC error consists of several items, each
starting with •. The first item is the message of pg-schema itself: which
table, which field and what is wrong with it. The following items
(In a stmt of a 'do' block, In the first argument of …) show where in the
code the error happened, and usually point at the call of an operation
(selectSch, insertJSON and so on) rather than at the type definition: a row
type is checked where it meets an annotation. So one error in a type may give
several identical messages — one for each call that uses the type. You fix it
once, in the type definition, and all these messages go away. The table and
field from the first item help to find the type.
All examples below show only the first item of the message.
Names¶
The most common error is a typo in a name. If a table is not found, the message is short:
• In schema Sch.Sch the table NameNS "tut" "Users" is not defined.
If a field or a relation is not found, the compiler lists all columns and foreign keys of the table, so the right name is usually visible at once:
• In schema Sch
for table "tut" ->> "tasks"
name "titel" is not defined.
Valid values are:
Fields: project_id, seq, title, priority, payload, watchers.
Foreign key constraints: tasks_project_id_fkey, task_events_project_id_seq_fkey.
Your source or target type or renamer is probably invalid.
Both the name in the message and the list of valid names are database names. A
name from your code first goes through the renamer (which turns a Haskell name
into a database name), and the message shows the result. With titel you do
not notice it, but if you write "ownerID" instead of "ownerId", the renamer
CamelToSnake turns it into owner_i_d, and the compiler reports
name "owner_i_d" is not defined. If the message shows an unexpected name,
check what the renamer makes of what you wrote. The same message appears for
names in conditions and ordering, for record fields and for PgTag fields.
For a relation name in qPath the message shows both the database name (in
quotes) and what was written in the code (in parentheses), and splits the valid
relations by direction:
• In schema Sch
for table NameNS "tut" "projects"
relation "tasks_project_fkey" ("tasksProjectFkey" after applying the renamer) is not defined.
Valid values are:
Foreign key constraints to parent tables (qPathFromHere or qPath): projects_owner_id_fkey.
Foreign key constraints from child tables (qPathToHere or qPath): project_log_project_id_fkey, tasks_project_id_fkey.
For a table related to itself qPath does not fit, and the compiler suggests
what to use instead (see the chapter Select for details):
• qPath cannot be used for self-reference relation.
Use qPathFromHere or qPathToHere.
Table: NameNS "tut" "users"
Relation: "users_manager_id_fkey"
Field types and NULL¶
The Haskell type of a field must match the column type. If a column allows
NULL, the field must be Maybe, and if it does not, it must not be:
• You can't use Maybe for mandatory fields
Table: NameNS "tut" "users"
DB Field name: "name"
DB Field type: NameNS "pg_catalog" "text"
Haskell Field type: Maybe Text
• You have to use Maybe for nullable fields
Table: NameNS "tut" "users"
DB Field name: "email"
DB Field type: NameNS "pg_catalog" "text"
Haskell Field type: Text
The same rule applies to values in conditions: a nullable column must be
compared with a value of type Maybe, for example "managerId" =? Just 1. The
message is the same, "You have to use Maybe for nullable fields".
If the Haskell type simply does not fit the column type, the message is less friendly:
• Could not solve: ‘CanConvert1
Sch
(NameNS "tut" "users")
"email"
(NameNS "pg_catalog" "text")
(TypDef "S" Nothing '[])
Int’
Read it like this: for the table tut.users and the field email of type
pg_catalog.text there is no conversion to Int (TypDef "S" … describes the
type in the database, and S means a string type). So the field type must be
changed to a suitable one, here Text. Which Haskell types fit which PostgreSQL
types is listed in the appendix Data types.
The error looks similar when the type of a value in a condition is not set, for
example "priority" <? 50 without an annotation: a numeric literal can be of
any type, and the compiler does not know which one to pick. The message shows a
variable like v0 instead of a Haskell type:
• Could not deduce ‘CanConvertMaybe
Sch
(NameNS "tut" "tasks")
"priority"
(NameNS "pg_catalog" "int4")
False
(TypDef "N" Nothing '[])
v0’
arising from a use of ‘<?’
It is enough to give the type: "priority" <? (50 :: Int32).
If you forgot to derive Generic for a record, the message is long and
obscure, with internal pg-schema names like WalkLevelAnn and GToJSONCols.
You can recognize it by the fragment GHC.Internal.Generics.Rep Task: Rep is
the representation of a type via Generic, and since it does not reduce, the
instance is missing.
Aggregates¶
For aggregates the result type of the SQL function is checked. For example,
avg over an integer column returns numeric, and Double does not fit it:
• Aggregate 'avg' over a field of type 'int4' returns SQL type 'numeric',
but Haskell type Double is not compatible with it.
Use Scientific or Rational instead.
Table: NameNS "tut" "tasks"
DB Field name: "priority"
DB Field type: NameNS "pg_catalog" "int4"
Haskell Field type: Aggr AAvg (Maybe Double)
Recall from the chapter Select that Aggr is used in queries without grouping and
requires Maybe inside, and Aggr' in queries with grouping. If Maybe is
needed, it goes inside the aggregate (Aggr AMax (Maybe Int32)), not outside
(Maybe (Aggr' AMax Int32)):
• You can't use Maybe for aggregates. Use Maybe inside Aggr'
Table: NameNS "tut" "tasks"
DB Field name: "priority"
DB Field type: NameNS "pg_catalog" "int4"
Haskell Field type: Aggr' AMax Int32
Mandatory columns and keys¶
For an insert, the input data must contain all mandatory columns, that is,
those declared NOT NULL and having no default value:
• We can't insert data because not all mandatory fields are present.
Table: NameNS "tut" "projects"
Missing mandatory fields: '["title"]
The same error appears after a mandatory column is added to the table and
Sch.hs is regenerated: the compiler shows every place where rows are inserted
into this table. upsertByKey gives the same error if a record contains only a
key: the row may turn out to be new, and there is nothing to insert it with. To
update a row giving only its key, use updateByKey. (upsertJSON behaves
differently here: a row without all mandatory columns is not inserted but only
updated by key, which is why Maybe appears in its result.)
Updating rows by key means updateByKey, updateJSON, and also upsertJSON
for rows that lack some mandatory columns. All of them need a full primary or
unique key. If there is none, the compiler lists the possible keys:
• We can't upsert data because for table NameNS "tut" "tasks"
not all mandatory fields are present and no full primary or
unique key is covered by record fields.
Missing mandatory fields: ["seq", "title"]
Possible keys: '[["project_id", "seq"]]
The message is shared by several operations, so it talks about upsert and
mandatory columns even if you called updateByKey or updateJSON. For them
the last line matters: one of the listed keys must be entirely in the type. In
operations with related tables, columns that come from the parent (here
project_id) need not be in the type.
Result shape of tree operations¶
In upsertJSON and updateJSON a row that is updated by key may not be found,
and then its place in the result holds Nothing. So the type of such a row in
the result must be Maybe. The words "update or key-only upsert" in the
message mean exactly this: updateJSON, or an upsertJSON row that has only a
key. For child rows the compiler says it like this:
• Returning must use Maybe here (update or key-only upsert).
At path: '[]
Table: NameNS "tut" "projects"
Field: "tasksProjectIdFkey"
Got: ("seq" := Int32) :. ("priority" := Int32)
For the root of upsertJSON without all mandatory columns, the message
explains the reason in more detail:
• Upsert returning row must be wrapped in Maybe:
the input row lacks mandatory fields ["owner_id", "title"]
so it is only updated by key and a row that is not found is returned as null.
Table: NameNS "tut" "projects"
Got: ("id" := Int64)
:. ("tasksProjectIdFkey"
:= [("seq" := Int32) :. ("title" := Text)])
In the first case the fixed type looks like this:
"tasksProjectIdFkey" := [Maybe ("seq" := Int32 :. "priority" := Int32)],
that is, Maybe wraps each element of the list, not the whole list. The root of
the result of updateJSON must be Maybe too. But for a row that has all
mandatory columns Maybe is forbidden: such a row is inserted or updated via
insert … on conflict and is always present in the result. All these rules are
summarized in a table in the reference.
Query structure¶
qLimit, qOffset and qDistinct are allowed only at the root of a query and
in a qPath to child rows. In a qPath to the parent they make no sense: a row
has one parent, and there is nothing to limit. The message about it is,
unfortunately, not the clearest:
• Couldn't match type ‘pg-schema-0.8.1.1:PgSchema.Schema.RelOne’
with ‘pg-schema-0.8.1.1:PgSchema.Schema.RelMany’
arising from a use of ‘qLimit’
If you see RelOne and RelMany, check whether qLimit, qOffset or
qDistinct stands inside a qPath to the parent.
Helper functions¶
named from the chapter Single-table DML attaches a list of field names to the values of each
row. It checks that there are as many names as values in a row. If there are
fewer names, the last name would get several values at once:
• named: there are fewer field names than fields in the row.
The last name "status" is given to more than one field: PGEnum
Sch ("tut" ->> "project_status")
:. PgArr Text
UnsafeCol from the chapter Select describes a result field with an arbitrary SQL
expression. It checks that the expression has as many ? marks as there are
columns in the list. If there are more columns, the message is this one (and if
there are more ? marks, a similar one, worded the other way round):
• Count of field names in the list of fields is more than count of question marks in expression.
Summary¶
- The first item of a message is the message of
pg-schema; the others show the place in the code. - The name in a message is the database name, that is, after the renamer.
- One error in a type gives a message for each call, but is fixed once.
- Columns that allow
NULLneedMaybeboth in the field type and in the value of a condition. - A message about keys is read by its
Possible keysline. RelOne/RelManynext toqLimit,qOffsetorqDistinctmeans they stand in a branch to the parent.
What next¶
This chapter is useful as a reference for compiler messages. How to live with
pg-schema in a real project where the database schema changes is covered in
the chapter In practice, and the rules for result types and
the limitations are collected in the reference.