Issue: Model without auto increment primary key is not saved without force_insert=True
Author: ElvisTheKing
State: closed
Created: 2014-10-07 22:19:00

Model save() method is implemented in such a way that it only use insert statement if primary key is null, which is correct only if primary key is auto increment or sequence field.

for example

```
class Object(BaseModel):
    uuid = CharField(primary_key=True)

object = Object(uuid="some-text-uuid")
object.save()
#returns 0
```

save issues an update statement, which obviously updates zero rows

I think insert should be implemented using "INSERT ... ON DUPLICATE KEY UPDATE" for mysql and sequential UPDATE-INSERT for sqlite and postgress (not really sure about these two)


This is known and documented behavior: http://docs.peewee-orm.com/en/latest/peewee/models.html#id3

You can always implement a custom method to perform this query if you are using MySQL (or create a mysql_ext module for the playhouse).

In the events where I've needed "get object or create if it does not exist" I've used something like this: http://docs.peewee-orm.com/en/latest/peewee/querying.html#get-or-create

I believe you could repurpose that code to attempt to INSERT then UPDATE on IntegrityError:

``` python
try:
    with db.transaction():
        SomeModel.insert(pk_val=primary_key, other_val=something_else).execute()
except peewee.IntegrityError:
    with db.transaction():
       SomeModel.update(other_val=something_else).where(
           SomeModel.pk_val == primary_key).execute()
```


While not being exactly what you asked for, hopefully the helpers in 073364a prove useful to you as building blocks.


@coleifer Could you explain why this commit was reverted shortly after? The docs state that "create or get" should be used instead of `create_or_get`, but only the latter seems to be supported in code.

One small issue I did notice with the code example, it relies on `IntegrityError` but doesn't check if the error was caused by a unique constraint, potentially causing `get` fallback on the wrong condition.


Ugh, I wish I can remember... I think I decided maybe just to keep stuff like that for the application layer developers rather than library code.


I think `create_or_get()` functionality still has a place within the core, but could be difficult to implement properly due to #639, and would require string comparison (due to missing error code on py3.4), which is a bit dirty.

The documentation recommends that "create or get" is better than `get_or_create`, but doesn't offer any ability to implement it in a clean fashion (due to missing error code), or any out of the box solution since this commit was reversed. The examples show that catching `IntegrityError` is enough, but this could lead to surprising results (e.g. an error occurs which is unrelated to unique constraints, but still raises `IntegrityError`, the except block then tries to fetch a row that doesn't exist).

Although I concur that `create_or_get` is far superior to `get_or_create`, we're perhaps not making it easy for people to adopt this correct approach, and the recommended documentation could lead to surprising behaviour if error codes are not checked. Or perhaps I'm thinking into it too much :)

Do you think it's worth re-visiting or should be left as is?


Yeah, reopening. Good points for sure.


Just FYI, my wife and I had our first baby a few days ago so I'm going to be kinda AWOL for a while.


Congratulations! I'll do some testing and submit an updated proposal, might be a few weeks though as I'll have to test each database etc.


Added create/get in
- be32fac431e75a1d9e5e0d06bcd0c9ff85a76b51 
- 4566c0d494d54a1a2d5e8b3a16ea2673b236f5af


LGTM. How would you feel about adding an additional check against the error code of `exc.args[0]` (to ensure it got a unique constraint error) of supported databases? I'm happy to submit a fully tested PR for this if needed.


I think I will pass on that for now, seems like a bit of a can of worms. If you do want your app to check for a specific error, you may just add your own implementation of `create_or_get`. FWIW, the semantics here are similar to Django, so that's why I'm OK with it.


This is really a weird behavior, comparing with all other ORM I have used.