2012-11-01 88 views
3

我試圖創建一個使用UUID_SHORT()作爲主鍵的表。我有一個觸發器,當你插入時插入一個值。我無法使sqlalchemy將一列識別爲primary_key而不抱怨不提供默認值。如果我確實包含默認值,即使在聲明server_default=FetchedValue()後,它也會使用該默認值。我看起來能夠正常工作的唯一方法就是如果列不是主鍵。SQLAlchemy的FetchedValue和primary_key

我使用的是金字塔,SQLAlchemy的ORM,和MySQL。

這裏的模型對象:

Base = declarative_base() 
class Patient(Base): 
    __tablename__ = 'patient' 
    patient_id = Column(BigInteger(unsigned=True), server_default=FetchedValue(), primary_key=True, autoincrement=False) 
    details = Column(Binary(10000)) 
在initializedb.py

我:

with transaction.manager: 
    patient1 = Patient(details = None) 
    DBSession.add(patient1) 
    DBSession.flush() 
    print(patient1.patient_id) 

運行../bin/initialize_mainserver_db development.ini給了我以下錯誤:

2012-11-01 20:17:22,168 INFO [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit) 
2012-11-01 20:17:22,169 INFO [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO patient (details) VALUES (%(details)s) 
2012-11-01 20:17:22,169 INFO [sqlalchemy.engine.base.Engine][MainThread] {'details': None} 
2012-11-01 20:17:22,170 INFO [sqlalchemy.engine.base.Engine][MainThread] ROLLBACK 
Traceback (most recent call last): 
    File "/sites/metrics_dev/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 1691, in _execute_context 
    context) 
    File "/sites/metrics_dev/lib/python3.3/site-packages/sqlalchemy/engine/default.py", line 333, in do_execute 
    cursor.execute(statement, parameters) 
    File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 418, in execute 
    self._handle_result(self._connection.cmd_query(stmt)) 
    File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 345, in _handle_result 
    self._handle_noresultset(result) 
    File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 321, in _handle_noresultset 
    self._warnings = self._fetch_warnings() 
    File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 608, in _fetch_warnings 
    raise errors.get_mysql_exception(res[0][1],res[0][2]) 
mysql.connector.errors.DatabaseError: 1364: Field 'patient_id' doesn't have a default value 

運行使用手動插入MySQL客戶端導致一切工作正常,所以問題看到ms與SQLAlchemy配合使用。

mysql> insert into patient(details) values (null); 
Query OK, 1 row affected, 1 warning (0.00 sec) 

mysql> select * from patient; 
+-------------------+---------+ 
| patient_id  | details | 
+-------------------+---------+ 
| 94732327996882980 | NULL | 
+-------------------+---------+ 
1 row in set (0.00 sec) 

mysql> show triggers; 
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+ 
| Trigger    | Event | Table | Statement       | Timing | Created | sql_mode | Definer  | character_set_client | collation_connection | Database Collation | 
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+ 
| before_insert_patient | INSERT | patient | SET new.`patient_id` = UUID_SHORT() | BEFORE | NULL |   | [email protected] | utf8     | utf8_general_ci  | latin1_swedish_ci | 
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+ 
1 row in set (0.00 sec) 
+0

我已經刪除了我的答案,因爲這是明顯的垃圾。對於MySQL,如你所說,基於觸發器的解決方案將是唯一能夠工作的解決方案。如果在此之前沒有人給出答案,我會在稍後提供給你。沒有機會切換到「正確的」DBMS(即PostgreSQL)? :) –

+0

感謝您的努力,雖然... :)我很欣賞,當有人正在嘗試幫助。 –

回答

2

這裏就是我所做的變通......

DBSession.execute(
"""CREATE TRIGGER before_insert_patient BEFORE INSERT ON `patient` 
    FOR EACH ROW BEGIN 
     IF (NEW.patient_id IS NULL OR NEW.patient_id = 0) THEN 
      SET NEW.patient_id = UUID_SHORT(); 
     END IF; 
    END""") 

,並在病人類:

patient_id = Column(BigInteger(unsigned=True), default=text("uuid_short()"), primary_key=True, autoincrement=False, server_default="0") 

因此,tr如果有人直接訪問數據庫,而不是通過python代碼,igger只會執行某些操作。並希望沒人能patient1 = Patient(patient_id=0, details = None)爲SQLAlchemy的將用「0」值,而不是什麼觸發產生

1

爲了完整起見,這裏是你的問題(也可here)根據您的回答另外兩個可能的解決方案。他們比你的解決方案稍微簡單(省略傳遞參數與正確的默認值),並使用SQLAlchemy的結構定義的觸發器。

#!/usr/bin/env python3 

from sqlalchemy import BigInteger, Column, create_engine, DDL, event 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker 
from sqlalchemy.schema import FetchedValue 
from sqlalchemy.sql.expression import func 

Base = declarative_base() 

class PatientOutputMixin(object): 
    ''' 
    Mixin to output human readable representations of models. 
    ''' 
    def __str__(self): 
     return '{}'.format(self.patient_id) 

    def __repr__(self): 
     return str(self) 


class Patient1(Base, PatientOutputMixin): 
    ''' 
    First version of ``Patient`` model. 
    ''' 
    __tablename__ = 'patient_1' 

    patient_id = Column(BigInteger, primary_key=True, 
         default=func.uuid_short()) 

# the following trigger is only required if columns are inserted in the table 
# not using the above model/table definition, otherwise it is redundant 
create_before_insert_trigger = DDL(''' 
CREATE TRIGGER before_insert_%(table)s BEFORE INSERT ON %(table)s 
FOR EACH ROW BEGIN 
    IF NEW.patient_id IS NULL THEN 
     SET NEW.patient_id = UUID_SHORT(); 
    END IF; 
END 
''') 

event.listen(Patient1.__table__, 'after_create', 
      create_before_insert_trigger.execute_if(dialect='mysql')) 
# end of optional trigger definition 


class Patient2(Base, PatientOutputMixin): 
    ''' 
    Second version of ``Patient`` model. 
    ''' 
    __tablename__ = 'patient_2' 

    patient_id = Column(BigInteger, primary_key=True, 
         default=0, server_default=FetchedValue()) 

create_before_insert_trigger = DDL(''' 
CREATE TRIGGER before_insert_%(table)s BEFORE INSERT ON %(table)s 
FOR EACH ROW BEGIN 
    SET NEW.patient_id = UUID_SHORT(); 
END 
''') 

event.listen(Patient2.__table__, 'after_create', 
      create_before_insert_trigger.execute_if(dialect='mysql')) 


# test models 

engine = create_engine('mysql+oursql://test:[email protected]/test?charset=utf8') 

Base.metadata.bind = engine 
Base.metadata.drop_all() 
Base.metadata.create_all() 

Session = sessionmaker(bind=engine) 

session = Session() 

for patient_model in [Patient1, Patient2]: 
    session.add(patient_model()) 
    session.add(patient_model()) 
    session.commit() 

    print('{} instances: {}'.format(patient_model.__name__, 
            session.query(patient_model).all())) 

運行上面的腳本產生以下(樣品)輸出:

Patient1 instances: [22681783426351145, 22681783426351146] 
Patient2 instances: [22681783426351147, 22681783426351148] 
+0

請參閱我的修改建議,讓我知道它是否有效。 –

+0

完全重做的答案,基於你的。可能會給你一個或兩個改進想法。 :) –