У вас включен AdBlock или иной блокировщик рекламы.

Пожалуйста, отключите его, доход от рекламы помогает развитию сайта и появлению новых статей.

Спасибо за понимание.

В другой раз
DevGang блог о програмировании
Авторизоваться

Создаем сокращенные ссылки с помощью SQLAlchemy и PosgreSQL 

Я хотел сохранить внешний URL проекта в виде короткой ссылки (немного похожей на битовую), чтобы вредоносный URL мог глобально блокироваться в системе. Чтобы избежать угадывания URL-адреса, я использовал hashids, который генерируют короткие уникальные непоследовательные идентификаторы из чисел. С помощью хешей я могу легко преобразовать идентификатор первичного ключа в альтернативный идентификатор, который может быть предоставлен.

http://mydomain.com/AW3d -> http://google.com 
(301 Moved Permanently to http://google.com)  

Чтобы проиллюстрировать нашу проблему, у нас есть модель под названием Link. Эта модель используется для хранения URL-адреса и соответствующего хеш-кода для короткого URL-адреса.

from sqlalchemy.ext.declarative import declared_attr, as_declarative
from sqlalchemy import Unicode, String, Column, Integer@as_declarative
class Base(object):
   @declared_attr
   def __tablename__(cls):
       return cls.__name__.lower()

   id = Column(Integer, primary_key=True)

engine = create_engine('postgresql://test@/test')
DBSession = scoped_session(sessionmaker(bind=engine))
Base.metadata.bind = engine

class Link(Base):
   url = Column('url', Unicode, index=True, unique=True, nullable=False)
   hashid = Column('url', String, index=True, unique=True, nullable=False)

Даже если блок транзакции защитит нас от случайной вставки из-за ошибки приложения на полпути. Я хочу, чтобы избежать обнуляемого столбца, если я могу сохранить целостность нашей базы данных. В некоторых случаях наличие значения NULL может иметь смысл, но нам не нужен объект ссылки без хеша.

Чтобы иметь возможность создать хеш-код перед вставкой, нам нужно знать, какой будет основной идентификатор. К счастью, в PostgreSQL есть последовательности, и когда вы определяете столбец Integer первичного ключа, SQLAlchemy автоматически использует тип SERIAL. SERIAL - это не настоящие типы, а удобство записи для создания столбцов с уникальным идентификатором. За сценой Postgres создает непустой столбец Integer со значением по умолчанию, равным следующему значению последовательности.

CREATE TABLE tablename (
   colname SERIAL
);

эквивалентно записи:

CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
   colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

Давайте сначала посмотрим, решение, если бы мы позволили NULL для столбца хэш Функция для создания объекта Link может выглядеть следующим образом:

from hashids import Hashids

def create_link(url):
   hashids = Hashids()
   link = Link(
       id=nextid,
       url=url,
       )
   DBSession.add(link)
   DBSession.flush(link)
   link.hashid = hashids.encode(link.id)
   DBSession.commit(link)
   return link

В нашем случае мы решили не допускать NULL в столбце hashid, поэтому приведенный выше код даст нам ошибку нарушения ограничения not-null.

Тип SERIAL создал последовательность формата tablename_columm_seq и, используя последовательность, мы можем предварительно выбрать id. Переписанная функция для создания объекта Link с использованием sequence может выглядеть следующим образом:

from hashids import Hashids
from sqlalchemy.schema import Sequence

def create_link(url):
   hashids = Hashids()
   nextid = DBSession.execute(Sequence("link_id_seq"))
   hashid = hashids.encode(nextid)
   link = Link(
       id=nextid,
       url=url,
       hashid=hashid
   )
   DBSession.add(link)
   DBSession.commit(link)
   return link

Поскольку последовательности не являются транзакционными, проблем с предварительной загрузкой идентификатора из него не возникает. Я не знаю ни о каких проблемах, которые могли бы случиться.

Я надеюсь, что этот пост может помочь, если вы столкнетесь с подобным сценарием использования, я могу представить себе ситуацию, когда я хотел узнать идентификатор, прежде чем вставить свою запись.

#PostgreSQL #Python