piccolo icon indicating copy to clipboard operation
piccolo copied to clipboard

Support for SQLite pragma and strict table settings

Open badlydrawnrob opened this issue 1 year ago • 3 comments

Apologies if this is somewhere in the documentation, but I can't find it. I'm looking for a replacement for Peewee (which has pragma and strict_tables support) that's async with FastAPI. Do you support these settings?

Also, a little confused about this SQLite async page. Is this only relevant if you're running a select then an insert/update/delete (in that order?) — for example, if you were to run an insert first, then a select, within the same function, you can just follow the regular docs?

badlydrawnrob avatar Apr 18 '25 17:04 badlydrawnrob

We don't have a way of creating strict tables currently with Piccolo.

Strict tables would be problematic for some of our column types (like Array) - we take advantage of the fact that you can specify arbitrary column types in SQLite. For example, by defining a column as an Array we know we have to parse that back into a list.

If working from an existing database with strict tables it should work OK, as long as you keep to the narrower set of column types (Integer, Real, Text, Blob).

With PRAGMAs, we do currently set some:

https://github.com/piccolo-orm/piccolo/blob/9517f49eef60a3e90bea21f8d25f3d99b6126181/piccolo/engine/sqlite.py#L696

We could extend this pretty easily to allow the user to specify their own PRAGMA statements. Presumably you would want to use this to enable strict mode.

Also, a little confused about this SQLite async page. Is this only relevant if you're running a select then an insert/update/delete (in that order?) — for example, if you were to run an insert first, then a select, within the same function, you can just follow the regular docs?

Yes, exactly. It's just a problem with SQLite if you have multiple DEFERRED transactions, and they do a SELECT followed by an INSERT / UPDATE / DELETE. You may never run into this problem - it is a weird edge case with SQLite.

dantownsend avatar Apr 19 '25 11:04 dantownsend

Hmm. I guess strict_tables could be added manually ... but understand your point about flexibility.

We take advantage of the fact that you can specify arbitrary column types in SQLite (like Array)

  • Are these Arrays stored as a json blob in SQLite? Or a plain string?
  • Is that in your documentation someplace, like in this blog post?

I'm guessing you could switch it out for JSONB for strict tables.

Types

My main concern is catching types before they get added to the SQLite database, as it's far from type safe. I had my model setup in Peewee ORM but the insert didn't fail (A String was supposed to be an Integer) — it was my mistake in the code, but I didn't catch it. It's why I like Elm lang, as I don't have to worry too much about screwing up!

My follow-up questions would be:

  1. Does Piccalo ALWAYS catch wrong types (like FastAPI does with Pydantic) or do I have to be extra careful with my code?
  2. Is migration a problem? Or will Postgres types be similar to SQLite ones?

PRAGMA

That'd be super helpful to add support. So far I've been using {"journal_mode": "wal", "cache_size": -65536, "foreign_keys": 1} with Peewee. I'm sure down the road there'll be others needed. I'm using SQLite for prototyping and eventually migrate to Postgres.

Async

Yes, exactly. It's just a problem with SQLite if ...

So it seems like better to just use async and wait until you discover the database locked issue?

badlydrawnrob avatar Apr 22 '25 15:04 badlydrawnrob

My main concern is catching types before they get added to the SQLite database, as it's far from type safe. I had my model setup in Peewee ORM but the insert didn't fail (A String was supposed to be an Integer) — it was my mistake in the code, but I didn't catch it. It's why I like Elm lang, as I don't have to worry too much about screwing up!

My follow-up questions would be:

  1. Does Piccalo ALWAYS catch wrong types (like FastAPI does with Pydantic) or do I have to be extra careful with my code?
  2. Is migration a problem? Or will Postgres types be similar to SQLite ones?

Piccolo will not catch wrong types before inserting (we can insert Integer type into Varchar column), but we can always use Pydantic to do validation. It is easy to use Pydantic with Piccolo. We have two options, write Pydantic models ourselves or use Piccolo utility function create_pydantic_model. Here is a small example script

Example
import asyncio

from piccolo.columns import Varchar, Array
from piccolo.table import Table, create_db_tables
from piccolo.utils.pydantic import create_pydantic_model
from piccolo.engine.sqlite import SQLiteEngine
from pydantic import BaseModel


DB = SQLiteEngine()


# Table example
class Manager(Table, db=DB):
    name = Varchar()
    arr_column = Array(base_column=Varchar())


# Models using Piccolo
ManagerModelIn = create_pydantic_model(table=Manager)

# Models using Pydantic
# class ManagerModelIn(BaseModel):
#     name: str
#     arr_column: list[str]


async def main():
    # Table creating
    await create_db_tables(Manager, if_not_exists=True)
    # data = ManagerModelIn(name=1, arr_column=[1, 2])
    # will raise ValidationError because we
    # are using the wrong types (integer, not string)
    data = ManagerModelIn(name="John", arr_column=["a", "b"]) # correct
    manager = Manager(**data.model_dump())
    await manager.save()
    # select data
    print(await Manager.select())


if __name__ == "__main__":
    asyncio.run(main())

sinisaos avatar Apr 23 '25 14:04 sinisaos