Настройка DDL

В предыдущих разделах мы обсудили различные конструкции схемы, включая Table, ForeignKeyConstraint, CheckConstraint и Sequence. Для создания языка определения данных (DDL) для всех конструкций мы использовали методы create() и create_all() из Table и MetaData. При выдаче вызывается заранее определенный порядок операций, и DDL для создания каждой таблицы создается безусловно, включая все ограничения и другие объекты, связанные с ней. Для более сложных сценариев, когда требуется DDL для конкретной базы данных, SQLAlchemy предлагает две техники, которые можно использовать для добавления любого DDL на основе любого условия, либо сопровождающего стандартную генерацию таблиц, либо самостоятельно.

Пользовательский DDL

Пользовательские DDL-фразы легче всего получить с помощью конструкции DDL. Эта конструкция работает как все остальные элементы DDL, за исключением того, что она принимает строку, которая является текстом, который должен быть выдан:

event.listen(
    metadata,
    "after_create",
    DDL(
        "ALTER TABLE users ADD CONSTRAINT "
        "cst_user_name_length "
        " CHECK (length(user_name) >= 8)"
    ),
)

Более полным методом создания библиотек DDL-конструкций является использование пользовательской компиляции - подробности см. в Пользовательские SQL-конструкции и расширение компиляции.

Управление последовательностями DDL

Конструкция DDL, представленная ранее, также имеет возможность быть вызванной условно на основе проверки базы данных. Эта возможность доступна с помощью метода DDLElement.execute_if(). Например, если бы мы хотели создать триггер, но только на бэкенде PostgreSQL, мы могли бы вызвать его следующим образом:

mytable = Table(
    "mytable",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("data", String(50)),
)

func = DDL(
    "CREATE FUNCTION my_func() "
    "RETURNS TRIGGER AS $$ "
    "BEGIN "
    "NEW.data := 'ins'; "
    "RETURN NEW; "
    "END; $$ LANGUAGE PLPGSQL"
)

trigger = DDL(
    "CREATE TRIGGER dt_ins BEFORE INSERT ON mytable "
    "FOR EACH ROW EXECUTE PROCEDURE my_func();"
)

event.listen(mytable, "after_create", func.execute_if(dialect="postgresql"))

event.listen(mytable, "after_create", trigger.execute_if(dialect="postgresql"))

Ключевое слово DDLElement.execute_if.dialect также принимает кортеж имен строковых диалектов:

event.listen(
    mytable, "after_create", trigger.execute_if(dialect=("postgresql", "mysql"))
)
event.listen(
    mytable, "before_drop", trigger.execute_if(dialect=("postgresql", "mysql"))
)

Метод DDLElement.execute_if() также может работать с вызываемой функцией, которая будет получать используемое соединение с базой данных. В примере ниже мы используем этот метод для условного создания ограничения CHECK, сначала просматривая каталоги PostgreSQL на предмет его существования:

def should_create(ddl, target, connection, **kw):
    row = connection.execute(
        "select conname from pg_constraint where conname='%s'" % ddl.element.name
    ).scalar()
    return not bool(row)


def should_drop(ddl, target, connection, **kw):
    return not should_create(ddl, target, connection, **kw)


event.listen(
    users,
    "after_create",
    DDL(
        "ALTER TABLE users ADD CONSTRAINT "
        "cst_user_name_length CHECK (length(user_name) >= 8)"
    ).execute_if(callable_=should_create),
)
event.listen(
    users,
    "before_drop",
    DDL("ALTER TABLE users DROP CONSTRAINT cst_user_name_length").execute_if(
        callable_=should_drop
    ),
)

sqlusers.create(engine)

sqlusers.drop(engine)

Использование встроенных классов DDLElement

Пакет sqlalchemy.schema содержит конструкции выражений SQL, которые предоставляют выражения DDL. Например, для создания оператора CREATE TABLE:

from sqlalchemy.schema import CreateTable
with engine.connect() as conn:
sql    conn.execute(CreateTable(mytable))

Выше, конструкция CreateTable работает как любая другая конструкция выражения (такая как select(), table.insert() и т.д.). Все конструкции SQLAlchemy, ориентированные на DDL, являются подклассами базового класса DDLElement; это основа всех объектов, соответствующих CREATE и DROP, а также ALTER, не только в SQLAlchemy, но и в Alembic Migrations. Полный справочник доступных конструкций находится в API Конструкции выражений DDL.

Определяемые пользователем конструкции DDL также могут быть созданы как подклассы самого DDLElement. В документации к Пользовательские SQL-конструкции и расширение компиляции есть несколько примеров этого.

Система событийного DDL, описанная в предыдущем разделе Управление последовательностями DDL, доступна и для других объектов DDLElement. Однако при работе со встроенными конструкциями, такими как CreateIndex, CreateSequence и т.д., система событий имеет ограниченное применение, поскольку такие методы как Table.create() и MetaData.create_all() будут вызывать эти конструкции безусловно. В будущем выпуске SQLAlchemy система событий DDL, включая условное выполнение, будет учтена для встроенных конструкций, которые в настоящее время вызываются во всех случаях.

Мы можем проиллюстрировать пример событийно-управляемой системы с помощью конструкций AddConstraint и DropConstraint, поскольку событийно-управляемая система будет работать для ограничений CHECK и UNIQUE, используя их так же, как мы делали это в предыдущем примере DDLElement.execute_if():

def should_create(ddl, target, connection, **kw):
    row = connection.execute(
        "select conname from pg_constraint where conname='%s'" % ddl.element.name
    ).scalar()
    return not bool(row)


def should_drop(ddl, target, connection, **kw):
    return not should_create(ddl, target, connection, **kw)


event.listen(
    users, "after_create", AddConstraint(constraint).execute_if(callable_=should_create)
)
event.listen(
    users, "before_drop", DropConstraint(constraint).execute_if(callable_=should_drop)
)

sqlusers.create(engine)

sqlusers.drop(engine)

Хотя приведенный выше пример направлен против встроенных объектов AddConstraint и DropConstraint, основная польза событий DDL пока остается сфокусированной на использовании самой конструкции DDL, а также с пользовательскими подклассами DDLElement, которые еще не являются частью MetaData.create_all(), Table.create() и соответствующих процессов «drop».

API Конструкции выражений DDL

Вернуться на верх