Type affinity should not be confused with Data Type (which Sqlite calls Storage Class).
Every single value in Sqlite has a Storage Class, which is always either NULL, INTEGER, REAL, TEXT or BLOB.
4 is a value of the INTEGER storage class.
'xyz' is a value of the TEXT storage class.
Note that typeof() returns the Storage class of a value, not the type affinity of the column.
Since in Sqlite columns are not required to have a specific storage class, and you can store both 4 or 'xyz' in the same column, Sqlite introduced the concept of Type Affinity, which is an indication of which kind of values SHOULD be stored in that column, if possible.
So if you declare a column
amount DOUBLE PRECISION
you are actually saying that amount should contain REAL values (has a REAL type affinity), but the actual values can be of any type.
Sqlite tries to satisfy this affinity when you insert a value of '4' into amount: it converts it to a REAL value before storing it. But if you insert a value of 'xyz', it cannot convert it to a REAL and it will store the value as a TEXT.
When you use the column amount in a query, Sqlite presumes that the values stored in the column should be REAL values. So when it does the comparison amount = '4' , it will apply rules for type affinity: '4' will be converted to a REAL and compared to what is stored in the column.
Since type affinity is dictated by the column definition at create time, and not by the single values contained, this implies that:
A. Type affinity of amount will always be REAL, even when the value contained in a row is 'xyz'.
B. Anything that is not a column has never been declared with a type affinity, so any expression that is not a simple reference to a column of a real table, or is not explictly CASTed, doesn't have a type affinity.
This means that 4 and '4' have no type affinity (even if they are constants of the INTEGER and TEXT storage class, respectively).
Also amount+10, sum(amount) or min(amount) are all expressions that don't have a type affinity (you didn't declare to Sqlite which type those expressions should be), but, when evaluated for every single row, they will evaluate to a value in a specific storage class.
This is why:
4 = '4' is FALSE, because neither has a type affinity and sqlite compares an integer with a string
amount = '4' is TRUE when a 4 is stored in the row, because since amount has a type affinity of REAL, sqlite expects all the values contained should be real and converts '4' to 4 before the comparison.
sum(amount) = '4' is FALSE even when the sum is actually 4, because sum(amount) is not a simple reference to a column, it's an expression and thus it has no type affinity. 4 is then compared to '4' and the result is false as in the first example.
- but
CAST(sum(amount) AS REAL) = '4' is TRUE when the sum is actually 4, because the CAST assigns a REAL type affinity to the left hand expression, so sqlite knows it has to convert the right hand '4' to 4 before comparison.
Also, if I understand correctly, while storage classes are checked and evaluated at runtime (because different rows can have different amount values of different storage classes), I think type affinity is evaluated at prepare time, so any necessary conversion is actually established, for every row, before checking the values in the rows.