Skip to main content
SQLAlchemy support for ZomboDb
=============================

**Experimental** support for the ZomboDB query language with SQLAlchemy. Hard fork of [sqla_zdb](https://github.com/xxxbobrxxx/sqlalchemy_zombodb).

Please check out [Query Syntax](https://github.com/skftn/sqlalchemy_zdb#syntax) for an example of supported expressions.


## What you need

Product | Version
--- | ---
Postgres | 9.5
Elasticsearch | 1.7.1+ (not 2.0)
Python | 3.5+
ZomboDB | 3.1.12


## Quick Example

Example SQL table:

```sql
CREATE TABLE products (
id SERIAL8 NOT NULL PRIMARY KEY,
name text NOT NULL,
keywords varchar(64)[],
short_summary phrase,
long_description fulltext,
price bigint,
inventory_count integer,
discontinued boolean default false,
availability_date date,
author varchar(32)
);
```

SQLAlchemy model:

```python
from sqlalchemy_zdb.types import PHRASE, FULLTEXT, ZdbColumn

class Products(base):
__tablename__ = "products"

id = Column(BIGINT, nullable=False, primary_key=True)
name = Column(Unicode(), nullable=False)
keywords = Column(ARRAY(Unicode(64)))
short_summary = ZdbColumn(PHRASE())
long_description = ZdbColumn(FULLTEXT(41))
price = ZdbColumn(BIGINT())
inventory_count = Column(Integer())
discontinued = Column(Boolean(), default=False)
availability_date = Column(DateTime())
author = ZdbColumn(Unicode(32))
```

- `ZdbColumn` - Explicitly mark columns that are included in the ZomboDB index
- `FULLTEXT` - A zomboDB specific type
- `PHRASE` - A ZomboDB specific type

`session.metadata.create_all()` correctly creates this table. It also adds the ZomboDB index.

## Querying

`ZdbQuery` inherits from `sqlalchemy.orm.session.Query` and you may use it as such.

```python
from sqlalchemy_zdb import ZdbQuery
session = scoped_session(sessionmaker(...))

q = ZdbQuery(Products, session=session)
q = q.filter(Products.name == "foo")
q = q.filter(Products.author.like("bar"))
q = q.filter(Products.price.between(5, 10))
q = q.filter(Products.discontinued == False)

results = q.all()
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:"bar" and price:5 /to/ 10' AND
products.name = 'foo' AND products.discontinued = false
```

Note that both the `name` and `discontinued` columns were not included in the ZomboDB query, instead they appear as valid PgSQL. This is because they were not of type `ZdbColumn` during query compilation.

## Word to the wise

This extension is currently in alpha. If you decide to use this package, double check if the SQL queries generated are correct. Upon weird behaviour please submit an issue so I can look into it.

## Syntax

### EQUALS

```python
q = q.filter(Products.author == "foo bar")
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:"foo bar"'
```

Stacking 'equals' conditions on a `ZdbColumn(ARRAY(String(32)))` column:

```python
q = q.filter(Products.keywords == "foo")
q = q.filter(Products.keywords == "bar")
```
```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'keywords:"foo" and keywords:"bar"'
```

This would match rows that both have the `foo` AND `bar` keywords.

### GT/LT

```python
q = q.filter(Products.price > 5)
```
```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'price > 5'
```

### BETWEEN
```python
q = q.filter(Products.price.between(5, 14.5))
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'price:5 /to/ 14.5'
```

### LIKE

```python
q = q.filter(Products.author.like("foo"))
```
```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:"foo bar"'
```

When passed a regex object, produces the expression: `column:~"foo[a-z]"`
```python
q = q.filter(Products.author.like(re.compile("foo[a-z]")))
```
```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:~"foo[a-z]"'
```

### more_like_this
```python
q = q.filter(Products.author.match("foo"))
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:@"foo"'
```

### IN
```python
q = q.filter(Products.author.in_(["foo", "bar"]))
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> 'author:("foo","bar")'
```

This matches both authors named 'foo' OR 'bar'.

### #LIMIT

ZomboDB allows you to limit the number of rows that are returned from a text query, which is similar to Postgres' `SQL-level ORDER BY LIMIT OFFSET` clauses, but can be drastically more efficient because less data is being passed around between Elasticsearch and Postgres.

Syntax:

```
#limit(sort_field asc|desc, offset_val, limit_val)
```
E.g:
```sql
SELECT [...] FROM table
WHERE zdb('table', ctid) ==> '#limit(price asc, 0, 10) ....'
ORDER BY author_name ASC;
```

In order to construct such a query in SQLAlchemy, the query object must receive:


1. A limit using `limit()`
2. A `ZdbColumn` or `ZdbScore` using `order_by()`

Example #1 - using a column marked as `ZdbColumn` (*Products.price*)

```python
q = q.filter(Products.author.in_(["foo", "bar"]))
q = q.order_by(Products.price.desc(),
Products.availability_date.desc()).limit(1).offset(1)
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> '#limit(price desc, 1, 1) author:("foo","bar")'
ORDER BY products.price DESC, products.availability_date DESC
```

In other words, if you were previously already using `limit()` in conjunction with `order_by()` in your query building and the subject column is of type `ZdbColumn`, it'll try to bake a proper query for it.

Example #2 - using `ZdbScore`

```python
from sqlalchemy_zdb.types import ZdbScore

q = q.filter(Products.author.in_(["foo", "bar"]))
q = q.order_by(ZdbScore("asc"),
Products.availability_date.desc(),
Products.long_description.desc())
q = q.limit(1)
q = q.offset(1)
```

```sql
SELECT [...] FROM products
WHERE zdb('products', ctid) ==> '#limit(_score asc, 1, 1) author:("foo","bar")'
ORDER BY zdb_score('products', ctid) ASC, products.availability_date DESC, products.long_description DESC
```

More can be read about `#limit` in the [ZomboDB documentation](https://github.com/zombodb/zombodb/blob/master/SYNTAX.md#limitoffset-with-sorting).

## Constructing filters
If you want to have more control over your query, you may use `zdb_raw_query` directly.

Phrase query:

from sqlalchemy_zdb import zdb_raw_query, ZdbPhrase

session.query(Post.text)\
.filter(zdb_raw_query(Post.text == 'hey joe'))


SELECT post.text AS post_text
FROM post
WHERE zdb('post', ctid) ==> 'text:"hey joe"'

Boolean query:

pg_session.query(Post.text)\
.filter(zdb_raw_query(or_(Post.text == 'foo', and_(Post.text == 'bar', Post.text == 'fuzz'))))

SELECT post.text AS post_text
FROM post
WHERE zdb('post', ctid) ==> '(text:"foo" or (text:"bar" and text:"fuzz"))'

more\_like\_this:

pg_session.query(Post.text)\
.filter(zdb_raw_query(Post.text.match('foo')))

SELECT post.text AS post_text
FROM post
WHERE zdb('post', ctid) ==> 'text:@foo'

Comparison operations:

pg_session.query(Post.text)\
.filter(zdb_raw_query(Post.comments > 1))

SELECT post.text AS post_text
FROM post
WHERE zdb('post', ctid) ==> 'comments > 1'

Raw query:

pg_session.query(Post.text)\
.filter(zdb_raw_query(Post, 'text:(sports,box) or long_description:(wooden w/5 away) and comments < 10'))

SELECT post.text AS post_text
FROM post
WHERE zdb('post', ctid) ==> 'text:(sports,box) or long_description:(wooden w/5 away) and comments < 10'


Metadata

Release files for sqlalchemy-zdb 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for sqlalchemy-zdb 0.1.3
File Interpreter ABI Platform
sqlalchemy_zdb-0.1.3-py3-none-any.whl Python 3 none any Details

Release files / sqlalchemy_zdb-0.1.3-py3-none-any.whl

Download URL sqlalchemy_zdb-0.1.3-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b2c24289ecc7aba4a5188cdbc91be4f72dfd2c3825427dfa4df786dc99c6d657
BLAKE2b-256 checksum
How to use checksums
a1c6d35868088c52b9bc720f3de950a753bf6a8a9d9846415cb230630e89ec62
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Security for sqlalchemy-zdb 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page