Spec-Zone.ru › Django 1.11

Поля модели, специфичные для PostgreSQL

Все эти поля доступны из модуля django.contrib.postgres.fields.

ArrayField

class ArrayField(base_field, size=None, **options) [source]

Поле для хранения списков данных. Большинство типов полей могут быть использованы, просто передайте другой экземпляр поля в качестве base_field. Вы также можете указать size. ArrayField может быть вложен для хранения многомерных массивов.

Если вы зададите поле default, убедитесь, что это вызываемый объект, например, list (для пустого значения по умолчанию) или вызываемый объект, возвращающий список (например, функция). Неправильное использование default=[] создаёт изменяемое значение по умолчанию, которое используется всеми экземплярами ArrayField.

base_field

Это обязательный аргумент.

Определяет базовый тип данных и поведение для массива. Он должен быть экземпляром подкласса Field. Например, это может быть IntegerField или CharField. Разрешены большинство типов полей, за исключением тех, которые обрабатывают реляционные данные (ForeignKey, OneToOneField и ManyToManyField).

Возможна вложенность полей массивов — вы можете указать экземпляр ArrayField в качестве base_field. Например:

from django.db import models
from django.contrib.postgres.fields import ArrayField

class ChessBoard(models.Model):
    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

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

size

Это необязательный аргумент.

Если он указан, массив будет иметь максимальный размер, как указано. Это будет передано в базу данных, хотя PostgreSQL в настоящее время не накладывает ограничение.

Примечание

При вложенности ArrayField, используете ли вы параметр size или нет, PostgreSQL требует, чтобы массивы были прямоугольными:

from django.contrib.postgres.fields import ArrayField
from django.db import models

class Board(models.Model):
    pieces = ArrayField(ArrayField(models.IntegerField()))

# Valid
Board(pieces=[
    [2, 3],
    [2, 1],
])

# Not valid
Board(pieces=[
    [2, 3],
    [2],
])

Если требуются непрямоугольные формы, то базовое поле должно быть сделано nullable, а значения дополнены None.

Запрос к ArrayField

Для ArrayField существует несколько пользовательских поисковых запросов и преобразований. Мы воспользуемся следующей примерной моделью:

from django.db import models
from django.contrib.postgres.fields import ArrayField

class Post(models.Model):
    name = models.CharField(max_length=200)
    tags = ArrayField(models.CharField(max_length=200), blank=True)

    def __str__(self):  # __unicode__ on Python 2
        return self.name

contains

Поиск contains переопределён для ArrayField. Возвращаются объекты, где переданные значения являются подмножеством данных. Используется оператор SQL @>. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>

contained_by

Это обратное contains поиск — возвращаются объекты, где данные являются подмножеством переданных значений. Используется оператор SQL <@. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__contained_by=['thoughts', 'django'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>

overlap

Возвращает объекты, у которых данные имеют какие-либо совпадения со значениями, переданными в качестве аргументов. Используется оператор SQL &&. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__overlap=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>

len

Возвращает длину массива. Доступны те же поиски, что и для IntegerField. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])

>>> Post.objects.filter(tags__len=1)
<QuerySet [<Post: Second post>]>

Индексы преобразования

Этот класс преобразований позволяет индексировать массив в запросах. Можно использовать любые неотрицательные целые числа. Если индекс превышает size массива, ошибок не будет. Доступны те же поиски, что и для base_field. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])

>>> Post.objects.filter(tags__0='thoughts')
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__1__iexact='Django')
<QuerySet [<Post: First post>]>

>>> Post.objects.filter(tags__276='javascript')
<QuerySet []>

Примечание

PostgreSQL использует 1-основательную нумерацию индексов для полей массивов при записи SQL-запросов. Однако эти индексы и те, которые используются в slices, используют 0-основательную нумерацию для согласованности с Python.

Срезы преобразования

Этот класс преобразований позволяет извлекать фрагмент массива. Можно использовать любые два неотрицательных целых числа, разделённых знаком подчёркивания. Доступные поиски не изменяются. Например:

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['django', 'python', 'thoughts'])

>>> Post.objects.filter(tags__0_1=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__0_2__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

Примечание

PostgreSQL использует 1-основательную нумерацию индексов для полей массивов при записи SQL-запросов. Однако эти срезы и те, которые используются в indexes, используют 0-основательную нумерацию для согласованности с Python.

Многомерные массивы с индексами и срезами

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

Индексирование ArrayField

В настоящее время использование db_index создаст индекс btree. Это не особенно помогает в запросах. Более полезным индексом является индекс GIN, который следует создавать с помощью операции RunSQL.

Поля CIText

class CIText(**options) [source]
Введено в Django 1.11.

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

Чтобы использовать citext, используйте операцию CITextExtension для настройки расширения citext в PostgreSQL перед первой миграцией CreateModel.

Предоставляются несколько полей, использующих этот миксин:

class CICharField(**options) [source]
class CIEmailField(**options) [source]
class CITextField(**options) [source]

Эти поля наследуются от CharField, EmailField и TextField, соответственно.

max_length не будет применяться в базе данных, так как citext ведёт себя аналогично типу text PostgreSQL.

HStoreField

class HStoreField(**options) [source]

Поле для хранения пар ключ-значение. Используемый тип данных Python — dict. Ключи должны быть строками, а значения могут быть либо строками, либо значениями NULL (None в Python).

Для использования этого поля необходимо:

  1. Добавить 'django.contrib.postgres' в ваши INSTALLED_APPS.
  2. Настроить расширение hstore в PostgreSQL.

Вы увидите ошибку, подобную can't adapt type 'dict', если пропустите первый шаг, или type "hstore" does not exist, если пропустите второй.

Изменено в Django 1.11:

Добавлена возможность хранить значения NULL. Ранее они преобразовывались в строки.

Примечание

В некоторых случаях может быть полезно потребовать или ограничить допустимые ключи для данного поля. Это можно сделать с помощью KeysValidator.

Запросы к полю HStoreField

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

Мы будем использовать следующую примерную модель:

from django.contrib.postgres.fields import HStoreField
from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = HStoreField()

    def __str__(self):  # __unicode__ on Python 2
        return self.name

Поиск по ключу

Для запроса по заданному ключу просто используйте этот ключ как имя поиска:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})

>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

Вы можете объединить другие поиски после поиска по ключу:

>>> Dog.objects.filter(data__breed__contains='l')
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

Если ключ, по которому вы хотите выполнить поиск, совпадает с именем другого поиска, вам необходимо использовать поиск hstorefield.contains вместо него.

Предупреждение

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

contains

Поиск contains переопределён для HStoreField. Возвращаемые объекты — те, где все заданные dict пар ключ-значение содержатся в поле. Используется оператор SQL @>. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})

>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>

contained_by

Это обратный поиск contains — возвращаемые объекты — те, где пары ключ-значение в объекте являются подмножеством пар в переданном значении. Используется оператор SQL <@. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})

>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>

>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>

has_key

Возвращает объекты, где заданный ключ присутствует в данных. Используется оператор SQL ?. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})

>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>

has_any_keys

Возвращает объекты, где любой из заданных ключей присутствует в данных. Используется оператор SQL ?|. Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})

>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

has_keys

Возвращает объекты, где все заданные ключи присутствуют в данных. Используется оператор SQL ?&. Например:

>>> Dog.objects.create(name='Rufus', data={})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})

>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>

keys

Возвращает объекты, где массив ключей имеет заданное значение. Обратите внимание, что порядок не гарантирован, поэтому данная операция полезна в основном при использовании в сочетании с поисками по ArrayField. Используется функция SQL akeys(). Например:

>>> Dog.objects.create(name='Rufus', data={'toy': 'bone'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})

>>> Dog.objects.filter(data__keys__overlap=['breed', 'toy'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

values

Возвращает объекты, где массив значений имеет заданное значение. Обратите внимание, что порядок не гарантирован, поэтому данная операция полезна в основном при использовании в сочетании с поисками по ArrayField. Используется функция SQL avalues(). Например:

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})

>>> Dog.objects.filter(data__values__contains=['collie'])
<QuerySet [<Dog: Meg>]>

JSONField

class JSONField(encoder=None, **options) [source]

Поле для хранения данных, закодированных в формате JSON. В Python данные представлены в своём собственном формате: словари, списки, строки, числа, булевы значения и None.

encoder
Добавлено в Django 1.11.

Необязательный класс кодирования JSON для сериализации типов данных, не поддерживаемых стандартным сериализатором JSON (datetime, uuid, и т.д.). Например, вы можете использовать класс DjangoJSONEncoder или любой другой подкласс json.JSONEncoder.

При получении значения из базы данных оно будет в формате, выбранном пользовательским кодировщиком (чаще всего строка), поэтому вам потребуется предпринять дополнительные шаги, чтобы преобразовать значение обратно в исходный тип данных (Model.from_db() и Field.from_db_value() — две возможные точки подключения для этой цели). Ваша десериализация может потребовать учета того, что вы не можете быть уверены в типе входных данных. Например, вы рискуете получить datetime, который фактически был строкой, просто совпавшей по формату с форматом, выбранным для datetime.

Если вы задаёте для поля default, убедитесь, что это вызываемая функция, например dict (для пустого значения по умолчанию) или вызываемая функция, возвращающая словарь (например, функция). Неправильное использование default={} создаёт изменяемое значение по умолчанию, которое используется всеми экземплярами JSONField.

Примечание

PostgreSQL имеет два встроенных типа данных на основе JSON: json и jsonb. Основное различие между ними заключается в том, как они хранятся и как ими можно выполнять запросы. Поле json PostgreSQL хранит в виде исходного строкового представления JSON и должно декодироваться при запросе на основе ключей. Поле jsonb хранится на основе фактической структуры JSON, что позволяет индексирование. Цена — небольшая дополнительная стоимость при записи в поле jsonb. JSONField использует jsonb.

В результате это поле требует PostgreSQL ≥ 9.4.

Запросы к полю JSONField

Мы будем использовать следующую примерную модель:

from django.contrib.postgres.fields import JSONField
from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = JSONField()

    def __str__(self):  # __unicode__ on Python 2
        return self.name

Поиск по ключу, индексу и пути

Для запроса по заданному ключу словаря просто используйте этот ключ как имя поиска:

>>> Dog.objects.create(name='Rufus', data={
...     'breed': 'labrador',
...     'owner': {
...         'name': 'Bob',
...         'other_pets': [{
...             'name': 'Fishy',
...         }],
...     },
... })
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})

>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

Несколько ключей могут быть объединены для поиска по пути:

>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>

Если ключ является целым числом, он будет интерпретирован как поиск по индексу в массиве:

>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>

Если ключ, по которому вы хотите выполнить поиск, совпадает с именем другого поиска, используйте поиск jsonfield.contains вместо него.

Если используется только один ключ или индекс, используется оператор SQL ->. Если используются несколько операторов, используется оператор #>.

Предупреждение

Поскольку любой строкой может быть ключ в объекте JSON, любой поиск, кроме перечисленных ниже, будет интерпретирован как поиск по ключу. Ошибки не генерируются. Будьте внимательны к опечаткам и всегда проверяйте, что ваши запросы работают так, как вы ожидаете.

Операции с включением и ключами

JSONField использует поиски, связанные с включением и ключами, аналогичные HStoreField.

  • contains (принимает любой JSON, а не только словарь строк)
  • contained_by (принимает любой JSON, а не только словарь строк)
  • has_key
  • has_any_keys
  • has_keys

Поле диапазонов

Существует пять типов полей диапазонов, соответствующих встроенным типам диапазонов в PostgreSQL. Эти поля используются для хранения диапазона значений; например, начальной и конечной временных меток события или диапазона возрастов, для которых подходит определённая активность.

Все поля диапазона переводятся в объекты psycopg2 Range в Python, но также принимают кортежи в качестве входных данных, если информация о границах не требуется. По умолчанию используется нижняя граница включительно, верхняя граница исключающая; то есть [)

IntegerRangeField

class IntegerRangeField(**options) [source]

Хранит диапазон целых чисел. Основан на IntegerField. Представлен в базе данных как int4range и как NumericRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю; то есть [)

BigIntegerRangeField

class BigIntegerRangeField(**options) [source]

Хранит диапазон больших целых чисел. Основан на BigIntegerField. Представлен в базе данных как int8range и как NumericRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю; то есть [)

FloatRangeField

class FloatRangeField(**options) [source]

Хранит диапазон значений с плавающей точкой. Основан на FloatField. Представлен как numrange в базе данных и как NumericRange в Python.

DateTimeRangeField

class DateTimeRangeField(**options) [source]

Хранит диапазон временных меток. Основан на DateTimeField. Представлен как tstzrange в базе данных и как DateTimeTZRange в Python.

DateRangeField

class DateRangeField(**options) [source]

Хранит диапазон дат. Основан на DateField. Представлен как daterange в базе данных и как DateRange в Python.

Независимо от указанных границ при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, включающей нижнюю границу и исключающей верхнюю; то есть [)

Запросы к полям диапазона

Существует ряд пользовательских запросов и преобразований для полей диапазона. Они доступны для всех вышеперечисленных полей, но мы будем использовать следующую примерную модель:

from django.contrib.postgres.fields import IntegerRangeField
from django.db import models

class Event(models.Model):
    name = models.CharField(max_length=200)
    ages = IntegerRangeField()
    start = models.DateTimeField()

    def __str__(self):  # __unicode__ on Python 2
        return self.name

Мы также будем использовать следующие примерные объекты:

>>> import datetime
>>> from django.utils import timezone
>>> now = timezone.now()
>>> Event.objects.create(name='Soft play', ages=(0, 10), start=now)
>>> Event.objects.create(name='Pub trip', ages=(21, None), start=now - datetime.timedelta(days=1))

и NumericRange:

>>> from psycopg2.extras import NumericRange

Функции включения

Как и в случае с другими полями PostgreSQL, существуют три стандартных оператора включения: contains, contained_by и overlap, использующие операторы SQL @>, <@ и && соответственно.

contains
>>> Event.objects.filter(ages__contains=NumericRange(4, 5))
<QuerySet [<Event: Soft play>]>
contained_by
>>> Event.objects.filter(ages__contained_by=NumericRange(0, 15))
<QuerySet [<Event: Soft play>]>

Запрос contained_by также доступен для типов полей без диапазонов: IntegerField, BigIntegerField, FloatField, DateField и DateTimeField. Например:

>>> from psycopg2.extras import DateTimeTZRange
>>> Event.objects.filter(start__contained_by=DateTimeTZRange(
...     timezone.now() - datetime.timedelta(hours=1),
...     timezone.now() + datetime.timedelta(hours=1),
... )
<QuerySet [<Event: Soft play>]>
overlap
>>> Event.objects.filter(ages__overlap=NumericRange(8, 12))
<QuerySet [<Event: Soft play>]>

Функции сравнения

Поля диапазонов поддерживают стандартные запросы: lt, gt, lte и gte. Они не очень полезны — они сравнивают сначала нижние границы, а затем верхние границы только при необходимости. Это также стратегия, используемая для сортировки по полю диапазона. Лучше использовать специфические операторы сравнения диапазонов.

fully_lt

Возвращаемые диапазоны строго меньше переданного диапазона. Другими словами, все точки в возвращаемом диапазоне меньше всех точек в переданном диапазоне.

>>> Event.objects.filter(ages__fully_lt=NumericRange(11, 15))
<QuerySet [<Event: Soft play>]>
fully_gt

Возвращаемые диапазоны строго больше переданного диапазона. Другими словами, все точки в возвращаемом диапазоне больше всех точек в переданном диапазоне.

>>> Event.objects.filter(ages__fully_gt=NumericRange(11, 15))
<QuerySet [<Event: Pub trip>]>
not_lt

Возвращаемые диапазоны не содержат точек, меньших переданного диапазона, то есть нижняя граница возвращаемого диапазона по крайней мере равна нижней границе переданного диапазона.

>>> Event.objects.filter(ages__not_lt=NumericRange(0, 15))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>
not_gt

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

>>> Event.objects.filter(ages__not_gt=NumericRange(3, 10))
<QuerySet [<Event: Soft play>]>
adjacent_to

Возвращаемые диапазоны имеют общую границу с переданным диапазоном.

>>> Event.objects.filter(ages__adjacent_to=NumericRange(10, 21))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>

Запросы с использованием границ

Доступны три преобразования для использования в запросах. Вы можете извлечь нижнюю или верхнюю границу или запросить пустоту.

startswith

Возвращаемые объекты имеют заданную нижнюю границу. Может быть объединен с допустимыми запросами для базового поля.

>>> Event.objects.filter(ages__startswith=21)
<QuerySet [<Event: Pub trip>]>
endswith

Возвращаемые объекты имеют заданную верхнюю границу. Может быть объединен с допустимыми запросами для базового поля.

>>> Event.objects.filter(ages__endswith=10)
<QuerySet [<Event: Soft play>]>
isempty

Возвращаемые объекты являются пустыми диапазонами. Может быть объединен с допустимыми запросами для BooleanField.

>>> Event.objects.filter(ages__isempty=True)
<QuerySet []>

Определение собственных типов диапазонов

PostgreSQL позволяет определять пользовательские типы диапазонов. Реализации полей моделей и форм Django используют базовые классы ниже, а psycopg2 предоставляет register_range() для использования пользовательских типов диапазонов.

class RangeField(**options) [source]

Базовый класс для полей диапазона модели.

base_field

Класс поля модели для использования.

range_type

Тип диапазона psycopg2 для использования.

form_field

Класс поля формы для использования. Должен быть подклассом django.contrib.postgres.forms.BaseRangeField.

class django.contrib.postgres.forms.BaseRangeField

Базовый класс для полей диапазона формы.

base_field

Поле формы для использования.

range_type

Тип диапазона psycopg2 для использования.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/fields/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API