coleifer / peewee

a small, expressive orm -- supports postgresql, mysql, sqlite and cockroachdb
http://docs.peewee-orm.com/
MIT License
11.18k stars 1.37k forks source link

With a non-incrementing string type primary key, save(force_insert=True) is executed without saving the primary key #2864

Closed li3807 closed 7 months ago

li3807 commented 7 months ago

Model code(模型代码):

class UserInfoModel(BaseModel):
    """用户信息,基本信息和账号信息
       User information, basic information and account information
     """
    user_id = CharField(primary_key=True, null=False, max_length=64)
    nickname = CharField(max_length=64)
    profile_photo = CharField(null=True, max_length=255)
    name = CharField(max_length=32)
    status = IntegerField(default=0, null=False)
    update_time = DateTimeField(null=False, default=datetime.now())
    update_as = CharField(null=False, max_length=32, default="")
    create_time = DateTimeField(null=False, default=datetime.now())
    create_as = CharField(null=False, max_length=32, default="")

Test code(测试代码):

    user_new = UserInfoModel()
    user_new.user_id = str(uuid.uuid4())
    user_new.nickname = request_wrap.param("nickname")
    user_new.name = request_wrap.param("name")
    user_new.update_as = "陈迪"
    user_new.create_as = "多的"

    user_new.save(force_insert=True)

Execution log(执行日志):

DEBUG:peewee:('INSERT INTO `user_info` (`nickname`, `name`, `status`, `update_time`, `update_as`, `create_time`, `create_as`) VALUES (%s, %s, %s, %s, %s, %s, %s)', ['nickname', 'name', 0, datetime.datetime(2024, 4, 2, 15, 59, 19, 318567), '陈迪', datetime.datetime(2024, 4, 2, 15, 59, 19, 318567), '多的'])
DEBUG:peewee.pool:No connection available in pool.
DEBUG:peewee.pool:Created new connection 2313529749712.
DEBUG:peewee.pool:Returning 2313529749712 to pool.
INFO:werkzeug:127.0.0.1 - - [02/Apr/2024 16:21:44] "POST /api/user/create HTTP/1.1" 200 -
DEBUG:peewee:('INSERT INTO `user_info` (`nickname`, `name`, `status`, `update_time`, `update_as`, `create_time`, `create_as`) VALUES (%s, %s, %s, %s, %s, %s, %s)', ['nickname', 'name', 0, datetime.datetime(2024, 4, 2, 15, 59, 19, 318567), '陈迪', datetime.datetime(2024, 4, 2, 15, 59, 19, 318567), '多的'])
DEBUG:peewee.pool:Returning 2313529749712 to pool.

As you can see from the log, the insert statement does not include the user_id primary key. Check the source code: 可以看到日志,insert 语句没有包括 user_id 主键,查看源码:

 def save(self, force_insert=False, only=None):
        field_dict = self.__data__.copy()
        if self._meta.primary_key is not False:
            pk_field = self._meta.primary_key
            pk_value = self._pk
        else:
            pk_field = pk_value = None
        if only is not None:
            field_dict = self._prune_fields(field_dict, only)
        elif self._meta.only_save_dirty and not force_insert:
            field_dict = self._prune_fields(field_dict, self.dirty_fields)
            if not field_dict:
                self._dirty.clear()
                return False

        self._populate_unsaved_relations(field_dict)
        rows = 1

        if self._meta.auto_increment and pk_value is None:
            field_dict.pop(pk_field.name, None)

        if pk_value is not None and not force_insert:
            if self._meta.composite_key:
                for pk_part_name in pk_field.field_names:
                    field_dict.pop(pk_part_name, None)
            else:
                field_dict.pop(pk_field.name, None)
            if not field_dict:
                raise ValueError('no data to save!')
            rows = self.update(**field_dict).where(self._pk_expr()).execute()
        elif pk_field is not None:
            # 这里没有把主键增加到 field_dict 中,新增加这段代码就可以了
            # There's no primary key added to field_dict, just add this code
            field_dict[pk_field.name] = pk_value
            pk = self.insert(**field_dict).execute()
            if pk is not None and (self._meta.auto_increment or
                                   pk_value is None):
                self._pk = pk
                # Although we set the primary-key, do not mark it as dirty.
                self._dirty.discard(pk_field.name)
        else:
            self.insert(**field_dict).execute()

        self._dirty -= set(field_dict)  # Remove any fields we saved.
        return rows
coleifer commented 7 months ago

Looks fine to me, you must have a bug in your code. Please take time before opening issues.

from datetime import datetime
import uuid
from peewee import *

db = SqliteDatabase(':memory:')

class UserInfoModel(db.Model):
    user_id = CharField(primary_key=True, null=False, max_length=64)
    nickname = CharField(max_length=64)
    profile_photo = CharField(null=True, max_length=255)
    name = CharField(max_length=32)
    status = IntegerField(default=0, null=False)
    update_time = DateTimeField(null=False, default=datetime.now())
    update_as = CharField(null=False, max_length=32, default="")
    create_time = DateTimeField(null=False, default=datetime.now())
    create_as = CharField(null=False, max_length=32, default="")

db.create_tables([UserInfoModel])

import logging
logger = logging.getLogger('peewee')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())

user = UserInfoModel()
user.user_id = str(uuid.uuid4())
user.nickname = 'nick'
user.name = 'name'
user.update_as = '123'
user.create_as = '234'

user.save(force_insert=True)
print(user.user_id)

user_db = UserInfoModel.get(UserInfoModel.name == 'name')
print(user_db.user_id)

assert user.user_id == user_db.user_id

Example output:

('INSERT INTO "userinfomodel" ("user_id", "nickname", "name", "status", "update_time", "update_as", "create_time", "create_as") VALUES (?, ?, ?, ?, ?, ?, ?, ?)', ['1649e33a-8d67-4d57-9a89-f2e04ac2ca41', 'nick', 'name', 0, datetime.datetime(2024, 4, 2, 7, 47, 5, 921707), '123', datetime.datetime(2024, 4, 2, 7, 47, 5, 921717), '234'])
1649e33a-8d67-4d57-9a89-f2e04ac2ca41
('SELECT "t1"."user_id", "t1"."nickname", "t1"."profile_photo", "t1"."name", "t1"."status", "t1"."update_time", "t1"."update_as", "t1"."create_time", "t1"."create_as" FROM "userinfomodel" AS "t1" WHERE ("t1"."name" = ?) LIMIT ? OFFSET ?', ['name', 1, 0])
1649e33a-8d67-4d57-9a89-f2e04ac2ca41

The assertion succeeds.

li3807 commented 7 months ago

I created the BaseModel class to handle the problem of database selection, using the Mysql database。

··· def save(self, force_insert=False, only=None):

When copying a data dictionary, there is no primary key field

    field_dict = self.__data__.copy()

···