docs/documentation/getting-started/environment.mdx
This guide will walk you through setting up your environment to run queries against ParadeDB. Choose your preferred tool below:
<Accordion title="SQL">ParadeDB comes with a helpful procedure that creates a table populated with mock data to help you get started. Run the following command to create this table.
CALL paradedb.create_bm25_test_table(
schema_name => 'public',
table_name => 'mock_items'
);
Then, inspect the first 3 rows:
SELECT description, rating, category
FROM mock_items
LIMIT 3;
description | rating | category
--------------------------+--------+-------------
Ergonomic metal keyboard | 4 | Electronics
Plastic Keyboard | 4 | Electronics
Sleek running shoes | 5 | Footwear
(3 rows)
Next, let's create a BM25 index called search_idx on this table. A BM25 index is a covering index, which means that multiple columns can be included in the same index.
CREATE INDEX search_idx ON mock_items
USING bm25 (id, description, category, rating, in_stock, created_at, metadata, weight_range)
WITH (key_field='id');
You're all set! Try running some queries.
</Accordion> <Accordion title="Django"> To start you'll need a [Django](https://www.djangoproject.com/) project with [Psycopg](https://www.psycopg.org/) and [django-paradedb](https://pypi.org/project/django-paradedb/) installed. Run the following to create one:python3 -m venv .venv
source .venv/bin/activate
pip install django psycopg django-paradedb==0.5.0
python3 -m django startproject myproject .
python3 manage.py startapp myapp
In myproject/settings.py, add 'django.contrib.postgres' and 'myapp' to INSTALLED_APPS. Then, configure DATABASES["default"] to point to Postgres:
INSTALLED_APPS = [
...,
'django.contrib.postgres',
'myapp',
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mydatabase",
"USER": "myuser",
"PASSWORD": "mypassword",
"HOST": "localhost",
"PORT": "5432",
}
}
We can now add a model for ParadeDB's built-in test table and BM25 index:
from django.db import models
from django.contrib.postgres.fields import IntegerRangeField
from paradedb.indexes import BM25Index
from paradedb.queryset import ParadeDBManager
class MockItem(models.Model):
description = models.TextField(null=True, blank=True)
rating = models.IntegerField(null=True, blank=True)
category = models.CharField(max_length=255, null=True, blank=True)
in_stock = models.BooleanField(null=True, blank=True)
metadata = models.JSONField(null=True, blank=True)
created_at = models.DateTimeField(null=True, blank=True)
last_updated_date = models.DateField(null=True, blank=True)
latest_available_time = models.TimeField(null=True, blank=True)
weight_range = IntegerRangeField(null=True, blank=True)
objects = ParadeDBManager()
class Meta:
db_table = "mock_items"
indexes = [
BM25Index(
fields={
"id": {},
"description": {"tokenizer": "unicode_words"},
"category": {"tokenizer": "literal"},
"rating": {},
"in_stock": {},
"metadata": {"json_fields": {"fast": True}},
"created_at": {},
"last_updated_date": {},
"latest_available_time": {},
"weight_range": {},
},
key_field="id",
name="search_idx",
),
]
Run the migrations to create the table and index:
python3 manage.py makemigrations
python3 manage.py migrate
Now, open a Python shell with python3 manage.py shell and run the following command to populate mock_items.
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("""
CALL paradedb.create_bm25_test_table(
schema_name => 'public',
table_name => 'mock_items_tmp'
);
INSERT INTO public.mock_items
SELECT * FROM public.mock_items_tmp;
DROP TABLE public.mock_items_tmp;
""")
You're all set! Try running some queries in your Python shell.
</Accordion> <Accordion title="SQLAlchemy"> To get started, install [SQLAlchemy](https://www.sqlalchemy.org/), [Alembic](https://alembic.sqlalchemy.org/en/latest/), [Psycopg](https://www.psycopg.org/), and [sqlalchemy-paradedb](https://pypi.org/project/sqlalchemy-paradedb/).python3 -m venv .venv
source .venv/bin/activate
pip install sqlalchemy psycopg alembic sqlalchemy-paradedb==0.5.0
Initialize Alembic:
alembic init migrations
Then update the Alembic configuration to point to your database:
sqlalchemy.url = postgresql+psycopg://myuser:mypassword@localhost:5432/mydatabase
ParadeDB comes with a built-in test table that we'll run our queries against. Create a models.py file with a model and search index for that table:
from __future__ import annotations
from datetime import date, datetime, time
from typing import Any
from sqlalchemy import Boolean, Date, DateTime, Index, Integer, String, Text, Time
from sqlalchemy.dialects.postgresql import INT4RANGE, JSONB, Range
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from paradedb.sqlalchemy import indexing
class Base(DeclarativeBase):
pass
class MockItem(Base):
__tablename__ = "mock_items"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
rating: Mapped[int | None] = mapped_column(Integer, nullable=True)
category: Mapped[str | None] = mapped_column(String(255), nullable=True)
in_stock: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
metadata_: Mapped[dict[str, Any] | None] = mapped_column("metadata", JSONB, nullable=True)
created_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_updated_date: Mapped[date | None] = mapped_column(Date, nullable=True)
latest_available_time: Mapped[time | None] = mapped_column(Time, nullable=True)
weight_range: Mapped[Range[int] | None] = mapped_column(INT4RANGE, nullable=True)
Index(
"search_idx",
indexing.BM25Field(MockItem.id),
indexing.BM25Field(MockItem.description),
indexing.BM25Field(MockItem.category),
indexing.BM25Field(MockItem.rating),
indexing.BM25Field(MockItem.in_stock),
indexing.BM25Field(MockItem.metadata_),
indexing.BM25Field(MockItem.created_at),
indexing.BM25Field(MockItem.last_updated_date),
indexing.BM25Field(MockItem.latest_available_time),
indexing.BM25Field(MockItem.weight_range),
postgresql_using="bm25",
postgresql_with={"key_field": "id"},
)
Copy this configuration into your migrations/env.py:
from logging.config import fileConfig
from sqlalchemy import engine_from_config, text
from sqlalchemy import pool
from alembic import context
# This import is required for autogenerated ParadeDB migrations
# to work properly.
import paradedb.sqlalchemy.alembic # noqa: F401
from models import Base
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
# The ParadeDB Docker image comes pre-bundled with some popular
# extensions like PostGIS. PostGIS automatically creates a table
# called `spatial_ref_sys`. This tells Alembic not to drop it even
# though it isn't tracked in Alembic's metadata.
IGNORED_TABLES = {"spatial_ref_sys"}
def include_object(object, name, type_, reflected, compare_to):
if type_ == "table" and reflected and name in IGNORED_TABLES:
return False
return True
def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
# This prevents Alembic from modifying tables outside
# of the `public` schema.
connection.execute(text("SET search_path TO public"))
connection.commit()
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
Next, add a migration to create the mock_items test table. Create a blank migration in 0001_create_mock_items_table.py by running the following command:
alembic revision --rev-id 0001 -m "Create mock_items table"
Update the generated migration to create the table:
def upgrade() -> None:
"""Upgrade schema."""
op.execute(
"""
CALL paradedb.create_bm25_test_table(
schema_name => 'public',
table_name => 'mock_items'
)
"""
)
def downgrade() -> None:
"""Downgrade schema."""
op.execute("DROP TABLE IF EXISTS public.mock_items")
Then, run it with:
alembic upgrade head
Next, autogenerate a new migration to create the search index.
alembic revision --rev-id 0002 --autogenerate -m "Create search index on mock_items"
The generated migration should look like this:
"""add mock_items search index
Revision ID: 0002
Revises: 0001
Create Date: 2026-04-07 13:56:45.304941
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '0002'
down_revision: Union[str, Sequence[str], None] = '0001'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_bm25_index('search_idx', 'mock_items', ['id', 'description', 'category', 'rating', 'in_stock', 'metadata', 'created_at', 'last_updated_date', 'latest_available_time', 'weight_range'], key_field='id', table_schema='public')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_bm25_index('search_idx', if_exists=True, schema='public')
# ### end Alembic commands ###
Then run it with:
alembic upgrade head
Finally, run python and execute the following:
from models import MockItem
from sqlalchemy import create_engine
engine = create_engine('postgresql+psycopg://myuser:mypassword@localhost:5432/mydatabase')
You're all set! Try running some queries in your shell.
</Accordion> <Accordion title="Rails"> To get started, create a [Rails](https://rubyonrails.org/) app that uses PostgreSQL.rails new paradedb -d postgresql
cd paradedb
Add the rails-paradedb gem to your Gemfile:
gem "rails-paradedb", "0.6.0", require: "parade_db"
Then install it:
bundle install
Update config/database.yml to point to your ParadeDB database:
development:
adapter: postgresql
encoding: unicode
database: mydatabase
username: myuser
password: mypassword
host: localhost
port: 5432
ParadeDB comes with a built-in test table that we'll run our queries against. Generate a migration to create it:
rails generate migration CreateMockItemsTable
Update the generated migration to create mock_items:
def up
execute <<~SQL
CALL paradedb.create_bm25_test_table(
schema_name => 'public',
table_name => 'mock_items'
);
SQL
end
def down
drop_table :mock_items, if_exists: true
end
Next, create a model for the mock_items table in app/models/mock_item.rb:
class MockItem < ApplicationRecord
include ParadeDB::Model
self.table_name = "mock_items"
self.primary_key = "id"
end
Then, create a search index for that table in app/models/mock_item_index.rb:
class MockItemIndex < ParadeDB::Index
self.table_name = :mock_items
self.key_field = :id
self.index_name = :search_idx
self.fields = {
id: nil,
description: nil,
category: nil,
rating: nil,
in_stock: nil,
metadata: nil,
created_at: nil,
last_updated_date: nil,
latest_available_time: nil,
weight_range: nil
}
end
Generate a migration for the search index:
rails generate migration CreateMockItemsIndex
Update the generated migration to create the index:
def up
create_paradedb_index(MockItemIndex, if_not_exists: true)
end
def down
remove_bm25_index :mock_items, name: :search_idx, if_exists: true
end
Run the migrations:
rails db:migrate
You're all set! Open the Rails console and run some queries.
rails console