@@ -26,7 +26,7 @@ pip install -e .[test,dev]
2626- ` paradedb.sqlalchemy.search ` : ParadeDB predicates (` match_all ` , ` fuzzy ` , ` parse ` , ` more_like_this ` , etc.).
2727- ` paradedb.sqlalchemy.pdb ` : function wrappers (` score ` , ` snippet ` , ` snippets ` , ` agg ` ).
2828- ` paradedb.sqlalchemy.facets ` : aggregate/facet JSON builders and rows+facets helper.
29- - ` paradedb.sqlalchemy.select_with ` : select decorators for score/snippet columns.
29+ - ` paradedb.sqlalchemy.select_with ` : select decorators for score/snippet/snippet_positions columns.
3030- ` paradedb.sqlalchemy.alembic ` : Alembic operations for BM25 index lifecycle.
3131
3232## Quickstart
@@ -49,11 +49,65 @@ products_bm25_idx.create(engine)
4949stmt = select(Product.id, Product.description).where(search.match_any(Product.description, " running" , " shoes" ))
5050```
5151
52+ Index JSON keys using BM25Field expressions:
53+
54+ ``` python
55+ from paradedb.sqlalchemy import expr
56+
57+ products_bm25_idx = Index(
58+ " products_bm25_idx" ,
59+ indexing.BM25Field(Product.id),
60+ indexing.BM25Field(
61+ expr.json_text(Product.metadata, " color" ),
62+ tokenizer = indexing.tokenize.literal(alias = " metadata_color" ),
63+ ),
64+ indexing.BM25Field(
65+ expr.json_text(Product.metadata, " location" ),
66+ tokenizer = indexing.tokenize.literal(alias = " metadata_location" ),
67+ ),
68+ postgresql_using = " bm25" ,
69+ postgresql_with = {" key_field" : " id" },
70+ )
71+ ```
72+
73+ Tokenizer configs can use a Django/Rails-style structured shape:
74+
75+ ``` python
76+ products_bm25_idx = Index(
77+ " products_bm25_idx" ,
78+ indexing.BM25Field(Product.id),
79+ indexing.BM25Field(
80+ Product.description,
81+ tokenizer = indexing.tokenize.from_config(
82+ {
83+ " tokenizer" : " simple" ,
84+ " filters" : [" lowercase" , " stemmer" ],
85+ " stemmer" : " english" ,
86+ " alias" : " description_simple" ,
87+ }
88+ ),
89+ ),
90+ indexing.BM25Field(
91+ Product.description,
92+ tokenizer = indexing.tokenize.from_config(
93+ {
94+ " tokenizer" : " ngram" ,
95+ " args" : [3 , 8 ],
96+ " named_args" : {" prefix_only" : True },
97+ " alias" : " description_ngram" ,
98+ }
99+ ),
100+ ),
101+ postgresql_using = " bm25" ,
102+ postgresql_with = {" key_field" : " id" },
103+ )
104+ ```
105+
52106## Query APIs
53107
54108- Basic predicates: ` match_all ` , ` match_any ` , ` term ` , ` phrase ` , ` fuzzy ` , ` regex ` , ` all `
55109- Advanced predicates: ` parse ` , ` phrase_prefix ` , ` regex_phrase ` , ` near ` , ` proximity ` , ` more_like_this `
56- - Scoring/snippets: ` pdb.score ` , ` pdb.snippet ` , ` pdb.snippets ` , ` select_with.score ` , ` select_with.snippet `
110+ - Scoring/snippets: ` pdb.score ` , ` pdb.snippet ` , ` pdb.snippets ` , ` pdb.snippet_positions ` , ` select_with.score ` , ` select_with.snippet ` , ` select_with.snippet_positions `
57111- Aggregations/facets: ` facets.* ` builders + ` pdb.agg(...) `
58112- Rows + facets: ` facets.with_rows(...) `
59113
@@ -67,6 +121,8 @@ stmt = (
67121 select(
68122 pdb.agg(facets.value_count(field = " id" )).label(" count" ),
69123 pdb.agg(facets.avg(field = " rating" )).label(" avg_rating" ),
124+ pdb.agg(facets.percentiles(field = " rating" , percents = [50 , 95 ])).label(" rating_percentiles" ),
125+ pdb.agg(facets.top_hits(size = 2 , sort = [{" rating" : " desc" }], docvalue_fields = [" id" , " rating" ])).label(" top_hits" ),
70126 )
71127 .select_from(Product)
72128 .where(search.match_all(Product.description, " running" ))
@@ -85,8 +141,24 @@ Usage:
85141
86142``` python
87143op.create_bm25_index(" products_bm25_idx" , " products" , [" id" , " description" ], key_field = " id" )
144+ # Tokenizer-aware expressions are supported too:
145+ op.create_bm25_index(
146+ " products_bm25_idx" ,
147+ " products" ,
148+ [" id" , " ((description)::pdb.simple('alias=description_simple,lowercase=true'))" ],
149+ key_field = " id" ,
150+ )
151+ # Schema-aware operations:
152+ op.create_bm25_index(
153+ " products_bm25_idx" ,
154+ " products" ,
155+ [" id" , " description" ],
156+ key_field = " id" ,
157+ table_schema = " search" ,
158+ )
88159op.reindex_bm25(" products_bm25_idx" , concurrently = True )
89- op.drop_bm25_index(" products_bm25_idx" , if_exists = True )
160+ op.reindex_bm25(" products_bm25_idx" , concurrently = True , schema = " search" )
161+ op.drop_bm25_index(" products_bm25_idx" , if_exists = True , schema = " search" )
90162```
91163
92164## Validation and Guardrails
@@ -117,7 +189,7 @@ python -m pytest tests/unit
117189Integration tests (requires running ParadeDB):
118190
119191``` bash
120- PARADEDB_TEST_DSN=postgres://postgres:postgres@localhost:5432 /postgres python -m pytest -m integration
192+ PARADEDB_TEST_DSN=postgres://postgres:postgres@localhost:5443 /postgres python -m pytest -m integration
121193```
122194
123195## CI
0 commit comments