From 734b276352d2e2a0e4167b2cf40d2cc629c912d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Fri, 27 Feb 2026 21:37:33 -0500 Subject: [PATCH 1/2] Modernize documentation setup --- .github/workflows/build.yaml | 2 + .github/workflows/docs.yaml | 33 ++ README.md | 77 ++-- ci/build-docs | 32 -- ci/build-docs-examples | 48 --- docs.sh | 7 - docs/beam-templates/chinook.hs | 3 +- docs/beam-templates/chinookdml.hs | 5 +- docs/beam-templates/chinookdmlpg.hs | 31 +- docs/beam-templates/chinookdmlsqlite.hs | 29 +- docs/beam-templates/employee1out-agg.hs | 58 +-- docs/beam-templates/employee1out.hs | 48 ++- docs/beam-templates/employee1sql-agg.hs | 64 +-- docs/beam-templates/employee1sql.hs | 63 +-- docs/beam-templates/employee2out.hs | 156 +++---- docs/beam-templates/employee2sql.hs | 173 ++++---- docs/beam-templates/employee3common.hs | 103 +++-- docs/beam-templates/employee3out-1.hs | 3 +- docs/beam-templates/employee3out-2.hs | 3 +- docs/beam-templates/employee3out.hs | 9 +- docs/beam-templates/employee3sql-1.hs | 3 +- docs/beam-templates/employee3sql-2.hs | 3 +- docs/beam-templates/employee3sql.hs | 12 +- docs/markdown/beam_query.py | 475 ++++++++++++--------- docs/markdown/markdown_fenced_code_tabs.py | 258 +++++------ docs/poetry.lock | 448 ------------------- docs/pyproject.toml | 8 +- docs/tutorials/tutorial2.md | 93 +--- docs/tutorials/tutorial3.md | 19 +- docs/user-guide/backends/beam-postgres.md | 44 +- flake.lock | 14 + flake.nix | 165 +++++-- mkdocs.yml | 4 +- 33 files changed, 1034 insertions(+), 1459 deletions(-) create mode 100644 .github/workflows/docs.yaml delete mode 100755 ci/build-docs delete mode 100755 ci/build-docs-examples delete mode 100755 docs.sh delete mode 100644 docs/poetry.lock diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7f68322a4..7a713f7a4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,6 +6,8 @@ on: - 'docs/*/**' branches: [master] pull_request: + paths-ignore: + - 'docs/*/**' jobs: continuous-integration: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 000000000..f6e218fd5 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,33 @@ +name: Build Docs + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: DeterminateSystems/nix-installer-action@main + + - uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Build docs + run: nix build .#docs -L + + - name: Upload docs artifact + uses: actions/upload-artifact@v4 + with: + name: beam-docs + path: result/ + + - name: Deploy to GitHub Pages + if: github.ref == 'refs/heads/master' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./result diff --git a/README.md b/README.md index 6ee8ce4bb..312ad8aa5 100644 --- a/README.md +++ b/README.md @@ -53,50 +53,24 @@ For questions, feel free to join our [mailing list](https://groups.google.com/forum/#!forum/beam-discussion) or head over to `#haskell-beam` on freenode. -## A word on testing - -`beam-core` has in-depth unit tests to test query generation over an idealized -ANSI SQL-compliant backend. You may be concerned that there are no tests in -either `beam-sqlite` or `beam-postgres`. Do not be alarmed. The documentation -contains many, many examples of queries written over the sample Chinook -database, the schema for which can be found at -`beam-sqlite/examples/Chinook/Schema.hs`. The included `mkdocs` configuration -and custom `beam_query` python Markdown extension automatically run every query -in the documentation against a live database connection. Any errors in -serializion/deserialization or invalid syntax are caught while building the -documentation. Feel free to open pull-requests with additional examples/tests. - -Tests are written - -~~~markdown -!beam-query -```haskell -!example -do x <- all_ (customer chinookDb) -- chinookDb available under chinook and chinookdml examples - pure x -``` -~~~ - -The `!beam-query` declaration indicates this is markdown code block that -contains beam query code. The `!example` declaration indicates that this example -should be built against applicable backends and included in the code. The -`template_name` is either `chinook` or `chinookdml` (depending on whether you -have quest a query or a DML statement). For `chinook`, the included code should -produce a `Q` query. For `chinookdml`, the included code should be a monadic -action in a `MonadBeam`. The `requirements` can be used to select which backends -to run this against. See the documentation for examples. - ## Building the documentation Beam uses [`mkdocs`](https://www.mkdocs.org/) for its documentation generation. ### Requirements -* Python installation with [`mkdocs` module](https://pypi.org/project/mkdocs/) -* Alternatively, open the Nix Flake shell via `nix develop`. -Then run `build-docs.sh`. +The dependencies to build documentation are packaged via Nix. You can build the +documentation using: + +```console +nix build .#docs +``` -TODO: define Nix package for docs bundle. +or, if you want to see what's going on in great detail: + +```console +nix build .#docs -L +``` The documentation uses a custom Markdown preprocessor to automatically build examples against the canonical Chinook database. By default, beam will build @@ -128,3 +102,32 @@ to enabled_backends: - beam-sqlite ``` + +### Checking queries in documentation +The documentation contains many, many examples of queries written over the sample Chinook +database, the schema for which can be found at +`beam-sqlite/examples/Chinook/Schema.hs`. The included `mkdocs` configuration +and custom `beam_query` python Markdown extension automatically run every query +in the documentation against a live database connection. Any errors in +serializion/deserialization or invalid syntax are caught while building the +documentation. Feel free to open pull-requests with additional examples/tests. + +Tests are written + +~~~markdown +!beam-query +```haskell +!example +do x <- all_ (customer chinookDb) -- chinookDb available under chinook and chinookdml examples + pure x +``` +~~~ + +The `!beam-query` declaration indicates this is markdown code block that +contains beam query code. The `!example` declaration indicates that this example +should be built against applicable backends and included in the code. The +`template_name` is either `chinook` or `chinookdml` (depending on whether you +have quest a query or a DML statement). For `chinook`, the included code should +produce a `Q` query. For `chinookdml`, the included code should be a monadic +action in a `MonadBeam`. The `requirements` can be used to select which backends +to run this against. See the documentation for examples. diff --git a/ci/build-docs b/ci/build-docs deleted file mode 100755 index 3fb4c9522..000000000 --- a/ci/build-docs +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -set -e - -if [ ! -d "$HOME/venv/bin" ]; then - echo "Creating virtual environment" - virtualenv $HOME/venv -fi - -source $HOME/venv/bin/activate - -pip install mkdocs==0.17.2 mkdocs-material==2.6.0 -pip install --upgrade awscli -pip install --upgrade sqlparse - -fetch_archive() { - BEAM_DOC_BACKEND=$1 - BEAM_DOC_CACHE_ARCHIVE="${BEAM_DOC_BACKEND}-docs-cache.tar.gz" - mkdir -p docs/.beam-query-cache - echo "Fetch ${BEAM_DOC_CACHE_ARCHIVE}" - aws s3 cp s3://beam-doc-cache/cache/${BEAM_DOC_CACHE_ARCHIVE} ./${BEAM_DOC_CACHE_ARCHIVE} - tar zxvf ./${BEAM_DOC_CACHE_ARCHIVE} -} - -echo "Building docs for ${BEAM_DOC_BACKEND}" - -echo "Attempting to download caches" -for backend in $BEAM_DOC_BACKENDS; do - fetch_archive $backend -done - -BEAM_DOC_BACKEND="${BEAM_DOC_BACKENDS}" PYTHONPATH="${PYTHONPATH}:." ./build-docs.sh builddocs diff --git a/ci/build-docs-examples b/ci/build-docs-examples deleted file mode 100755 index af06b06da..000000000 --- a/ci/build-docs-examples +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -set -e - -BEAM_DOC_CACHE_ARCHIVE="${BEAM_DOC_BACKEND}-docs-cache.tar.gz" -BEAM_DOC_CACHE="docs/${BEAM_DOC_CACHE_ARCHIVE}" - -if [ ! -d "$HOME/venv/bin" ]; then - echo "Creating virtual environment" - virtualenv $HOME/venv -fi - -source $HOME/venv/bin/activate - -pip install mkdocs==0.17.2 mkdocs-material==2.6.0 -pip install --upgrade awscli -pip install --upgrade sqlparse - -echo "Attempting to download cache" -mkdir -p docs/.beam-query-cache - -aws s3 cp s3://beam-doc-cache/cache/${BEAM_DOC_CACHE_ARCHIVE} ./beam-doc-cache.tar.gz || true -if [ -f ./beam-doc-cache.tar.gz ]; then - echo "Extracting cache" - tar zxvf ./beam-doc-cache.tar.gz - - echo "Removing cached backend data" - ls -d docs/.beam-query-cache/* | grep "/[A-Z][^\-]*\-[0-9a-f]\{32,32\}$" | xargs rm -fi - -echo "Building documentation examples for ${BEAM_DOC_BACKEND}" -PYTHONPATH="${PYTHONPATH}:." ./build-docs.sh builddocs - -echo "Setting up cache" - -ls -d docs/.beam-query-cache/* | grep "/\([A-Z][^\-]*\-\)\?[0-9a-f]\{32,32\}\(\.hs\)\?$" | xargs tar -c | gzip > $BEAM_DOC_CACHE - -DO_UPLOAD_CACHE=yes -if [ -f ./beam-doc-cache.tar.gz ]; then - if (diff ${BEAM_DOC_CACHE} ./beam-doc-cache.tar.gz); then - echo "Archives do not differ, not uploading new version" - DO_UPLOAD_CACHE=no - fi -fi - -if [ "$DO_UPLOAD_CACHE" = "yes" ]; then - aws s3 cp ${BEAM_DOC_CACHE} s3://beam-doc-cache/cache/${BEAM_DOC_CACHE_ARCHIVE} -fi diff --git a/docs.sh b/docs.sh deleted file mode 100755 index ceb42220c..000000000 --- a/docs.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -e - -dir=$(mktemp -d dist-docs.XXXXXX) - -# assumes cabal 2.4 or later -cabal v2-haddock beam-postgres --builddir="$dir" --haddock-for-hackage --enable-doc \ No newline at end of file diff --git a/docs/beam-templates/chinook.hs b/docs/beam-templates/chinook.hs index 3bb22ab0f..d168f64c7 100644 --- a/docs/beam-templates/chinook.hs +++ b/docs/beam-templates/chinook.hs @@ -1,6 +1,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RecursiveDo #-} --- ! BUILD_OPTIONS: -fglasgow-exts -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -XStandaloneDeriving -XFlexibleInstances -XMultiParamTypeClasses -XDeriveGeneric -XFlexibleContexts -fno-warn-partial-type-signatures -i$$BEAM_SOURCE$$/beam-sqlite/examples/ +-- ! BUILD_OPTIONS: -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -XStandaloneDeriving -XFlexibleInstances -XMultiParamTypeClasses -XDeriveGeneric -XFlexibleContexts -fno-warn-partial-type-signatures -i$$BEAM_SOURCE$$/beam-sqlite/examples/ -- ! BUILD_DIR: beam-sqlite/examples/ module Main where diff --git a/docs/beam-templates/chinookdml.hs b/docs/beam-templates/chinookdml.hs index ee9778c26..b93a61c44 100644 --- a/docs/beam-templates/chinookdml.hs +++ b/docs/beam-templates/chinookdml.hs @@ -1,6 +1,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RecursiveDo #-} --- ! BUILD_OPTIONS: -fglasgow-exts -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -XStandaloneDeriving -XFlexibleInstances -XMultiParamTypeClasses -XDeriveGeneric -XFlexibleContexts -fno-warn-partial-type-signatures -i$$BEAM_SOURCE$$/beam-sqlite/examples/ +-- ! BUILD_OPTIONS: -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -XStandaloneDeriving -XFlexibleInstances -XMultiParamTypeClasses -XDeriveGeneric -XFlexibleContexts -fno-warn-partial-type-signatures -i$$BEAM_SOURCE$$/beam-sqlite/examples/ -- ! BUILD_DIR: beam-sqlite/examples/ module Main where @@ -17,7 +18,7 @@ import Data.IORef import Data.Monoid ((<>)) import Data.Scientific (Scientific) import Data.Int -import Data.Text +import Data.Text hiding (show) import Chinook.Schema diff --git a/docs/beam-templates/chinookdmlpg.hs b/docs/beam-templates/chinookdmlpg.hs index 167791acd..4b8109064 100644 --- a/docs/beam-templates/chinookdmlpg.hs +++ b/docs/beam-templates/chinookdmlpg.hs @@ -1,7 +1,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -i../../beam-sqlite/examples -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -i../../beam-sqlite/examples -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-postgres/examples/ module Main where @@ -14,12 +14,12 @@ import Database.Beam.Postgres hiding (insert, runInsert) import qualified Database.Beam.Postgres as Pg import Database.PostgreSQL.Simple -import Control.Monad import Control.Exception +import Control.Monad import Data.IORef -import Data.Monoid import Data.Int +import Data.Monoid import Chinook.Schema @@ -33,20 +33,21 @@ exampleQuery putStrLn = do main :: IO () main = - do chinook <- connectPostgreSQL "dbname=chinook" + do + chinook <- connectPostgreSQL "dbname=chinook" - stmts <- newIORef id + stmts <- newIORef id - let onStmt s = modifyIORef stmts (. (s:)) - record a = withDatabaseDebug (onStmt . (++ ";")) chinook a + let onStmt s = modifyIORef stmts (. (s :)) + record a = withDatabaseDebug (onStmt . (++ ";")) chinook a - handle (\BeamDone -> pure ()) $ - withTransaction chinook $ do - record $ exampleQuery (liftIO . onStmt . ("-- Output: " ++)) - throwIO BeamDone + handle (\BeamDone -> pure ()) $ + withTransaction chinook $ do + record $ exampleQuery (liftIO . onStmt . ("-- Output: " ++)) + throwIO BeamDone - mkStmtList <- readIORef stmts - let stmtList = mkStmtList [] + mkStmtList <- readIORef stmts + let stmtList = mkStmtList [] - forM_ stmtList $ \stmt -> do - putStrLn stmt + forM_ stmtList $ \stmt -> do + putStrLn stmt diff --git a/docs/beam-templates/chinookdmlsqlite.hs b/docs/beam-templates/chinookdmlsqlite.hs index c06891611..e48ade273 100644 --- a/docs/beam-templates/chinookdmlsqlite.hs +++ b/docs/beam-templates/chinookdmlsqlite.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XTypeFamilies -XOverloadedStrings -XPartialTypeSignatures -XTypeApplications -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ module Main where @@ -11,8 +11,8 @@ import Database.Beam.Backend.Types import Database.Beam.Sqlite import Database.SQLite.Simple -import Control.Monad import Control.Exception +import Control.Monad import Data.IORef import Data.Int @@ -29,20 +29,21 @@ exampleQuery putStrLn = do main :: IO () main = - do chinook <- open "chinook.db" + do + chinook <- open "chinook.db" - stmts <- newIORef id + stmts <- newIORef id - let onStmt s = modifyIORef stmts (. (s:)) - record = withDatabaseDebug onStmt chinook + let onStmt s = modifyIORef stmts (. (s :)) + record = withDatabaseDebug onStmt chinook - handle (\BeamDone -> pure ()) $ - withTransaction chinook $ do - record $ exampleQuery (liftIO . onStmt . ("-- Output: " ++)) - throwIO BeamDone + handle (\BeamDone -> pure ()) $ + withTransaction chinook $ do + record $ exampleQuery (liftIO . onStmt . ("-- Output: " ++)) + throwIO BeamDone - mkStmtList <- readIORef stmts - let stmtList = mkStmtList [] + mkStmtList <- readIORef stmts + let stmtList = mkStmtList [] - forM_ stmtList $ \stmt -> do - putStrLn stmt + forM_ stmtList $ \stmt -> do + putStrLn stmt diff --git a/docs/beam-templates/employee1out-agg.hs b/docs/beam-templates/employee1out-agg.hs index 39684e61c..233a92f04 100644 --- a/docs/beam-templates/employee1out-agg.hs +++ b/docs/beam-templates/employee1out-agg.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -XCPP -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -XCPP -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: console module Main where @@ -12,33 +12,34 @@ import Database.Beam.Sqlite hiding (runBeamSqliteDebug) import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) } - deriving Generic + {_shoppingCartUsers :: f (TableEntity UserT)} + deriving (Generic) instance Database be ShoppingCartDb shoppingCartDb :: DatabaseSettings Sqlite ShoppingCartDb @@ -46,21 +47,24 @@ shoppingCartDb = defaultDbSettings main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} - , User "james@pallo.com" "James" "Pallo" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@sims.com" "Betty" "Sims" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "james@oreily.com" "James" "O'Reily" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "sam@sophitz.com" "Sam" "Sophitz" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} - , User "sam@jely.com" "Sam" "Jely" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} ] + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - let runBeamSqliteDebug _ = Sqlite.runBeamSqlite + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues + [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + , User "james@pallo.com" "James" "Pallo" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@sims.com" "Betty" "Sims" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "james@oreily.com" "James" "O'Reily" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "sam@sophitz.com" "Sam" "Sophitz" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + , User "sam@jely.com" "Sam" "Jely" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + ] - BEAM_PLACEHOLDER + let runBeamSqliteDebug _ = Sqlite.runBeamSqlite + BEAM_PLACEHOLDER diff --git a/docs/beam-templates/employee1out.hs b/docs/beam-templates/employee1out.hs index 46a92cae9..ac2a8fbc0 100644 --- a/docs/beam-templates/employee1out.hs +++ b/docs/beam-templates/employee1out.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: console module Main where @@ -12,33 +12,34 @@ import Database.Beam.Sqlite hiding (runBeamSqliteDebug) import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) } - deriving Generic + {_shoppingCartUsers :: f (TableEntity UserT)} + deriving (Generic) instance Database be ShoppingCartDb shoppingCartDb :: DatabaseSettings Sqlite ShoppingCartDb @@ -46,16 +47,19 @@ shoppingCartDb = defaultDbSettings main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} ] + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - let runBeamSqliteDebug _ = Sqlite.runBeamSqlite + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues + [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + ] - BEAM_PLACEHOLDER + let runBeamSqliteDebug _ = Sqlite.runBeamSqlite + BEAM_PLACEHOLDER diff --git a/docs/beam-templates/employee1sql-agg.hs b/docs/beam-templates/employee1sql-agg.hs index 1f0937140..b26b1799a 100644 --- a/docs/beam-templates/employee1sql-agg.hs +++ b/docs/beam-templates/employee1sql-agg.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: sql module Main where @@ -11,33 +11,34 @@ import Database.Beam.Sqlite hiding (runBeamSqliteDebug) import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) } - deriving Generic + {_shoppingCartUsers :: f (TableEntity UserT)} + deriving (Generic) instance Database be ShoppingCartDb @@ -46,24 +47,29 @@ shoppingCartDb = defaultDbSettings main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} - , User "james@pallo.com" "James" "Pallo" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@sims.com" "Betty" "Sims" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "james@oreily.com" "James" "O'Reily" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "sam@sophitz.com" "Sam" "Sophitz" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} - , User "sam@jely.com" "Sam" "Jely" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} ] + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues + [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + , User "james@pallo.com" "James" "Pallo" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@sims.com" "Betty" "Sims" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "james@oreily.com" "James" "O'Reily" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "sam@sophitz.com" "Sam" "Sophitz" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + , User "sam@jely.com" "Sam" "Jely" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + ] - let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn + let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn - (do let putStrLn :: String -> IO () - putStrLn _ = pure () + ( do + let putStrLn :: String -> IO () + putStrLn _ = pure () - BEAM_PLACEHOLDER - ) + BEAM_PLACEHOLDER + ) diff --git a/docs/beam-templates/employee1sql.hs b/docs/beam-templates/employee1sql.hs index f6cc0854f..9b685098d 100644 --- a/docs/beam-templates/employee1sql.hs +++ b/docs/beam-templates/employee1sql.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: sql module Main where @@ -11,33 +11,34 @@ import Database.Beam.Sqlite hiding (runBeamSqliteDebug) import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) } - deriving Generic + {_shoppingCartUsers :: f (TableEntity UserT)} + deriving (Generic) instance Database be ShoppingCartDb @@ -46,20 +47,24 @@ shoppingCartDb = defaultDbSettings main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} - , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} - , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} ] - - let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn - - - (do let putStrLn :: String -> IO () - putStrLn _ = pure () - - BEAM_PLACEHOLDER - ) + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" + + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues + [ User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" {- james -} + , User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" {- betty -} + , User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" {- sam -} + ] + + let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn + + ( do + let putStrLn :: String -> IO () + putStrLn _ = pure () + + BEAM_PLACEHOLDER + ) diff --git a/docs/beam-templates/employee2out.hs b/docs/beam-templates/employee2out.hs index 490448293..868d0b9c5 100644 --- a/docs/beam-templates/employee2out.hs +++ b/docs/beam-templates/employee2out.hs @@ -1,8 +1,8 @@ -{-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE NoMonomorphismRestriction #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: console module Main where @@ -16,21 +16,23 @@ import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple import Lens.Micro +import Lens.Micro.TH (makeLenses) -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User @@ -39,92 +41,94 @@ type UserId = PrimaryKey UserT Identity instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) +makeLenses ''UserT + data AddressT f = Address - { _addressId :: C f Int32 - , _addressLine1 :: C f Text - , _addressLine2 :: C f (Maybe Text) - , _addressCity :: C f Text - , _addressState :: C f Text - , _addressZip :: C f Text - - , _addressForUser :: PrimaryKey UserT f } - deriving Generic + { _addressId :: C f Int32 + , _addressLine1 :: C f Text + , _addressLine2 :: C f (Maybe Text) + , _addressCity :: C f Text + , _addressState :: C f Text + , _addressZip :: C f Text + , _addressForUser :: PrimaryKey UserT f + } + deriving (Generic) type Address = AddressT Identity deriving instance Show (PrimaryKey UserT Identity) deriving instance Show Address instance Table AddressT where - data PrimaryKey AddressT f = AddressId (Columnar f Int32) deriving Generic - primaryKey = AddressId . _addressId + data PrimaryKey AddressT f = AddressId (Columnar f Int32) deriving (Generic) + primaryKey = AddressId . _addressId instance Beamable AddressT instance Beamable (PrimaryKey AddressT) +makeLenses ''AddressT + data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) - , _shoppingCartUserAddresses :: f (TableEntity AddressT) } - deriving Generic + { _shoppingCartUsers :: f (TableEntity UserT) + , _shoppingCartUserAddresses :: f (TableEntity AddressT) + } + deriving (Generic) instance Database be ShoppingCartDb +makeLenses ''ShoppingCartDb + shoppingCartDb :: DatabaseSettings Sqlite ShoppingCartDb -shoppingCartDb = defaultDbSettings `withDbModification` - dbModification { - _shoppingCartUserAddresses = - modifyTable (\_ -> "addresses") $ - tableModification { - _addressLine1 = fieldNamed "address1", - _addressLine2 = fieldNamed "address2" - } - } +shoppingCartDb = + defaultDbSettings + `withDbModification` dbModification + { _shoppingCartUserAddresses = + setEntityName "addresses" <> + modifyTableFields + tableModification + { _addressLine1 = fieldNamed "address1" + , _addressLine2 = fieldNamed "address2" + } + } shoppingCartDb1 :: DatabaseSettings Sqlite ShoppingCartDb -shoppingCartDb1 = defaultDbSettings `withDbModification` - dbModification { - _shoppingCartUsers = modifyTable (\_ -> "users") tableModification, - _shoppingCartUserAddresses = modifyTable (\_ -> "user_addresses") tableModification - } - -Address (LensFor addressId) (LensFor addressLine1) - (LensFor addressLine2) (LensFor addressCity) - (LensFor addressState) (LensFor addressZip) - (UserId (LensFor addressForUserId)) = - tableLenses - -User (LensFor userEmail) (LensFor userFirstName) - (LensFor userLastName) (LensFor userPassword) = - tableLenses - -ShoppingCartDb (TableLens shoppingCartUsers) - (TableLens shoppingCartUserAddresses) = - dbLenses +shoppingCartDb1 = + defaultDbSettings + `withDbModification` dbModification + { _shoppingCartUsers = setEntityName "users" + , _shoppingCartUserAddresses = setEntityName "user_addresses" + } +userId :: Lens' (PrimaryKey UserT f) (Columnar f Text) +userId f' (UserId x) = UserId <$> f' x main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - execute_ conn "CREATE TABLE addresses ( id INTEGER PRIMARY KEY AUTOINCREMENT, address1 VARCHAR NOT NULL, address2 VARCHAR, city VARCHAR NOT NULL, state VARCHAR NOT NULL, zip VARCHAR NOT NULL, for_user__email VARCHAR NOT NULL );" - - let james = User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" - betty = User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" - sam = User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ james, betty, sam ] - - let addresses = [ Address default_ (val_ "123 Little Street") (val_ Nothing) (val_ "Boston") (val_ "MA") (val_ "12345") (pk james) - , Address default_ (val_ "222 Main Street") (val_ (Just "Ste 1")) (val_ "Houston") (val_ "TX") (val_ "8888") (pk betty) - , Address default_ (val_ "9999 Residence Ave") (val_ Nothing) (val_ "Sugarland") (val_ "TX") (val_ "8989") (pk betty) ] - - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUserAddresses shoppingCartDb) $ - insertExpressions addresses - - let runBeamSqliteDebug _ = Sqlite.runBeamSqlite - - BEAM_PLACEHOLDER - + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" + execute_ conn "CREATE TABLE addresses ( id INTEGER PRIMARY KEY AUTOINCREMENT, address1 VARCHAR NOT NULL, address2 VARCHAR, city VARCHAR NOT NULL, state VARCHAR NOT NULL, zip VARCHAR NOT NULL, for_user__email VARCHAR NOT NULL );" + + let james = User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" + betty = User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" + sam = User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues [james, betty, sam] + + let addresses = + [ Address default_ (val_ "123 Little Street") (val_ Nothing) (val_ "Boston") (val_ "MA") (val_ "12345") (pk james) + , Address default_ (val_ "222 Main Street") (val_ (Just "Ste 1")) (val_ "Houston") (val_ "TX") (val_ "8888") (pk betty) + , Address default_ (val_ "9999 Residence Ave") (val_ Nothing) (val_ "Sugarland") (val_ "TX") (val_ "8989") (pk betty) + ] + + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUserAddresses shoppingCartDb) $ + insertExpressions addresses + + let runBeamSqliteDebug _ = Sqlite.runBeamSqlite + + BEAM_PLACEHOLDER diff --git a/docs/beam-templates/employee2sql.hs b/docs/beam-templates/employee2sql.hs index d13e94294..14f1e3f98 100644 --- a/docs/beam-templates/employee2sql.hs +++ b/docs/beam-templates/employee2sql.hs @@ -1,8 +1,8 @@ -{-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE NoMonomorphismRestriction #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! FORMAT: sql module Main where @@ -16,121 +16,126 @@ import qualified Database.Beam.Sqlite as Sqlite import Database.SQLite.Simple import Lens.Micro +import Lens.Micro.TH (makeLenses) -import Data.Text (Text) import Data.Int +import Data.Text (Text) import Control.Monad import Data.IORef data UserT f - = User - { _userEmail :: Columnar f Text - , _userFirstName :: Columnar f Text - , _userLastName :: Columnar f Text - , _userPassword :: Columnar f Text } - deriving Generic + = User + { _userEmail :: Columnar f Text + , _userFirstName :: Columnar f Text + , _userLastName :: Columnar f Text + , _userPassword :: Columnar f Text + } + deriving (Generic) type User = UserT Identity deriving instance Show User deriving instance Eq User +type UserId = PrimaryKey UserT Identity + instance Beamable UserT instance Table UserT where - data PrimaryKey UserT f = UserId (Columnar f Text) deriving Generic - primaryKey = UserId . _userEmail + data PrimaryKey UserT f = UserId (Columnar f Text) deriving (Generic) + primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) -type UserId = PrimaryKey UserT Identity +makeLenses ''UserT data AddressT f = Address - { _addressId :: C f Int32 - , _addressLine1 :: C f Text - , _addressLine2 :: C f (Maybe Text) - , _addressCity :: C f Text - , _addressState :: C f Text - , _addressZip :: C f Text - - , _addressForUser :: PrimaryKey UserT f } - deriving Generic + { _addressId :: C f Int32 + , _addressLine1 :: C f Text + , _addressLine2 :: C f (Maybe Text) + , _addressCity :: C f Text + , _addressState :: C f Text + , _addressZip :: C f Text + , _addressForUser :: PrimaryKey UserT f + } + deriving (Generic) type Address = AddressT Identity deriving instance Show (PrimaryKey UserT Identity) deriving instance Show Address instance Table AddressT where - data PrimaryKey AddressT f = AddressId (Columnar f Int32) deriving Generic - primaryKey = AddressId . _addressId + data PrimaryKey AddressT f = AddressId (Columnar f Int32) deriving (Generic) + primaryKey = AddressId . _addressId instance Beamable AddressT instance Beamable (PrimaryKey AddressT) -data ShoppingCartDb f = ShoppingCartDb - { _shoppingCartUsers :: f (TableEntity UserT) - , _shoppingCartUserAddresses :: f (TableEntity AddressT) } - deriving Generic +makeLenses ''AddressT +data ShoppingCartDb f = ShoppingCartDb + { _shoppingCartUsers :: f (TableEntity UserT) + , _shoppingCartUserAddresses :: f (TableEntity AddressT) + } + deriving (Generic) instance Database be ShoppingCartDb +makeLenses ''ShoppingCartDb + shoppingCartDb :: DatabaseSettings Sqlite ShoppingCartDb -shoppingCartDb = defaultDbSettings `withDbModification` - dbModification { - _shoppingCartUserAddresses = - modifyTable (\_ -> "addresses") $ - tableModification { - _addressLine1 = fieldNamed "address1", - _addressLine2 = fieldNamed "address2" - } - } +shoppingCartDb = + defaultDbSettings + `withDbModification` dbModification + { _shoppingCartUserAddresses = + setEntityName "addresses" <> + modifyTableFields + tableModification + { _addressLine1 = fieldNamed "address1" + , _addressLine2 = fieldNamed "address2" + } + } shoppingCartDb1 :: DatabaseSettings Sqlite ShoppingCartDb -shoppingCartDb1 = defaultDbSettings `withDbModification` - dbModification { - _shoppingCartUsers = modifyTable (\_ -> "users") tableModification, - _shoppingCartUserAddresses = modifyTable (\_ -> "user_addresses") tableModification - } - -Address (LensFor addressId) (LensFor addressLine1) - (LensFor addressLine2) (LensFor addressCity) - (LensFor addressState) (LensFor addressZip) - (UserId (LensFor addressForUserId)) = - tableLenses - -User (LensFor userEmail) (LensFor userFirstName) - (LensFor userLastName) (LensFor userPassword) = - tableLenses - -ShoppingCartDb (TableLens shoppingCartUsers) - (TableLens shoppingCartUserAddresses) = - dbLenses +shoppingCartDb1 = + defaultDbSettings + `withDbModification` dbModification + { _shoppingCartUsers = setEntityName "users" + , _shoppingCartUserAddresses = setEntityName "user_addresses" + } + +userId :: Lens' (PrimaryKey UserT f) (Columnar f Text) +userId f' (UserId x) = UserId <$> f' x main :: IO () main = - do conn <- open ":memory:" - execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" - execute_ conn "CREATE TABLE addresses ( id INTEGER PRIMARY KEY AUTOINCREMENT, address1 VARCHAR NOT NULL, address2 VARCHAR, city VARCHAR NOT NULL, state VARCHAR NOT NULL, zip VARCHAR NOT NULL, for_user__email VARCHAR NOT NULL );" - - let james = User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" - betty = User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" - sam = User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUsers shoppingCartDb) $ - insertValues [ james, betty, sam ] - - let addresses = [ Address default_ (val_ "123 Little Street") (val_ Nothing) (val_ "Boston") (val_ "MA") (val_ "12345") (pk james) - , Address default_ (val_ "222 Main Street") (val_ (Just "Ste 1")) (val_ "Houston") (val_ "TX") (val_ "8888") (pk betty) - , Address default_ (val_ "9999 Residence Ave") (val_ Nothing) (val_ "Sugarland") (val_ "TX") (val_ "8989") (pk betty) ] - - runBeamSqlite conn $ runInsert $ - insert (_shoppingCartUserAddresses shoppingCartDb) $ - insertExpressions addresses - - let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn - - - (do let putStrLn :: String -> IO () - putStrLn _ = pure () - - print _ = pure () - - BEAM_PLACEHOLDER - ) + do + conn <- open ":memory:" + execute_ conn "CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));" + execute_ conn "CREATE TABLE addresses ( id INTEGER PRIMARY KEY AUTOINCREMENT, address1 VARCHAR NOT NULL, address2 VARCHAR, city VARCHAR NOT NULL, state VARCHAR NOT NULL, zip VARCHAR NOT NULL, for_user__email VARCHAR NOT NULL );" + + let james = User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c" + betty = User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f" + sam = User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c" + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUsers shoppingCartDb) $ + insertValues [james, betty, sam] + + let addresses = + [ Address default_ (val_ "123 Little Street") (val_ Nothing) (val_ "Boston") (val_ "MA") (val_ "12345") (pk james) + , Address default_ (val_ "222 Main Street") (val_ (Just "Ste 1")) (val_ "Houston") (val_ "TX") (val_ "8888") (pk betty) + , Address default_ (val_ "9999 Residence Ave") (val_ Nothing) (val_ "Sugarland") (val_ "TX") (val_ "8989") (pk betty) + ] + + runBeamSqlite conn $ + runInsert $ + insert (_shoppingCartUserAddresses shoppingCartDb) $ + insertExpressions addresses + + let runBeamSqliteDebug _ = Sqlite.runBeamSqliteDebug putStrLn + + ( do + let putStrLn :: String -> IO () + putStrLn _ = pure () + + print _ = pure () + + BEAM_PLACEHOLDER + ) diff --git a/docs/beam-templates/employee3common.hs b/docs/beam-templates/employee3common.hs index 3fa8b9733..8a8132cf8 100644 --- a/docs/beam-templates/employee3common.hs +++ b/docs/beam-templates/employee3common.hs @@ -1,6 +1,3 @@ -{-# LANGUAGE ImpredicativeTypes #-} -{-# LANGUAGE NoMonomorphismRestriction #-} - import Prelude hiding (lookup) import Database.Beam hiding (withDatabaseDebug) @@ -15,6 +12,7 @@ import Text.Read import Data.Time import Lens.Micro +import Lens.Micro.TH (makeLenses) import Data.Text (Text) import Data.Int @@ -44,6 +42,8 @@ instance Table UserT where primaryKey = UserId . _userEmail instance Beamable (PrimaryKey UserT) +makeLenses ''UserT + data AddressT f = Address { _addressId :: C f Int32 , _addressLine1 :: C f Text @@ -66,6 +66,8 @@ type AddressId = PrimaryKey AddressT Identity -- For convenience instance Beamable AddressT instance Beamable (PrimaryKey AddressT) +makeLenses ''AddressT + data ProductT f = Product { _productId :: C f Int32 , _productTitle :: C f Text @@ -84,23 +86,7 @@ instance Beamable ProductT instance Beamable (PrimaryKey ProductT) deriving instance Show (PrimaryKey AddressT Identity) -data OrderT f = Order - { _orderId :: Columnar f Int32 - , _orderDate :: Columnar f LocalTime - , _orderForUser :: PrimaryKey UserT f - , _orderShipToAddress :: PrimaryKey AddressT f - , _orderShippingInfo :: PrimaryKey ShippingInfoT (Nullable f) } - deriving Generic -type Order = OrderT Identity -deriving instance Show Order - -instance Table OrderT where - data PrimaryKey OrderT f = OrderId (Columnar f Int32) - deriving Generic - primaryKey = OrderId . _orderId - -instance Beamable OrderT -instance Beamable (PrimaryKey OrderT) +makeLenses ''ProductT data ShippingCarrier = USPS | FedEx | UPS | DHL deriving (Show, Read, Eq, Ord, Enum) @@ -131,6 +117,26 @@ instance Beamable ShippingInfoT instance Beamable (PrimaryKey ShippingInfoT) deriving instance Show (PrimaryKey ShippingInfoT (Nullable Identity)) +data OrderT f = Order + { _orderId :: Columnar f Int32 + , _orderDate :: Columnar f LocalTime + , _orderForUser :: PrimaryKey UserT f + , _orderShipToAddress :: PrimaryKey AddressT f + , _orderShippingInfo :: PrimaryKey ShippingInfoT (Nullable f) } + deriving Generic +type Order = OrderT Identity +deriving instance Show Order + +instance Table OrderT where + data PrimaryKey OrderT f = OrderId (Columnar f Int32) + deriving Generic + primaryKey = OrderId . _orderId + +instance Beamable OrderT +instance Beamable (PrimaryKey OrderT) + +makeLenses ''OrderT + deriving instance Show (PrimaryKey OrderT Identity) deriving instance Show (PrimaryKey ProductT Identity) @@ -150,6 +156,7 @@ instance Table LineItemT where instance Beamable LineItemT instance Beamable (PrimaryKey LineItemT) +makeLenses ''LineItemT data ShoppingCartDb f = ShoppingCartDb { _shoppingCartUsers :: f (TableEntity UserT) @@ -162,46 +169,36 @@ data ShoppingCartDb f = ShoppingCartDb instance Database be ShoppingCartDb -ShoppingCartDb (TableLens shoppingCartUsers) (TableLens shoppingCartUserAddresses) - (TableLens shoppingCartProducts) (TableLens shoppingCartOrders) - (TableLens shoppingCartShippingInfos) (TableLens shoppingCartLineItems) = dbLenses +makeLenses ''ShoppingCartDb shoppingCartDb :: DatabaseSettings be ShoppingCartDb shoppingCartDb = defaultDbSettings `withDbModification` dbModification { _shoppingCartUserAddresses = - modifyTable (\_ -> "addresses") $ - tableModification { - _addressLine1 = fieldNamed "address1", - _addressLine2 = fieldNamed "address2" - }, - _shoppingCartProducts = modifyTable (\_ -> "products") tableModification, - _shoppingCartOrders = modifyTable (\_ -> "orders") $ - tableModification { - _orderShippingInfo = ShippingInfoId "shipping_info__id" - }, - _shoppingCartShippingInfos = modifyTable (\_ -> "shipping_info") $ - tableModification { - _shippingInfoId = "id", - _shippingInfoCarrier = "carrier", - _shippingInfoTrackingNumber = "tracking_number" - }, - _shoppingCartLineItems = modifyTable (\_ -> "line_items") tableModification + setEntityName "addresses" + <> modifyTableFields + tableModification { + _addressLine1 = fieldNamed "address1", + _addressLine2 = fieldNamed "address2" + }, + _shoppingCartProducts = setEntityName "products", + _shoppingCartOrders = + setEntityName "orders" + <> modifyTableFields + tableModification { + _orderShippingInfo = ShippingInfoId "shipping_info__id" + }, + _shoppingCartShippingInfos = + setEntityName "shipping_info" + <> modifyTableFields + tableModification { + _shippingInfoId = "id", + _shippingInfoCarrier = "carrier", + _shippingInfoTrackingNumber = "tracking_number" + }, + _shoppingCartLineItems = setEntityName "line_items" } -Address (LensFor addressId) (LensFor addressLine1) - (LensFor addressLine2) (LensFor addressCity) - (LensFor addressState) (LensFor addressZip) - (UserId (LensFor addressForUserId)) = - tableLenses - -User (LensFor userEmail) (LensFor userFirstName) - (LensFor userLastName) (LensFor userPassword) = - tableLenses - -LineItem _ _ (LensFor lineItemQuantity) = tableLenses -Product (LensFor productId) (LensFor productTitle) (LensFor productDescription) (LensFor productPrice) = tableLenses - main :: IO () main = do conn <- open ":memory:" diff --git a/docs/beam-templates/employee3out-1.hs b/docs/beam-templates/employee3out-1.hs index 59a36efc9..d1ed51095 100644 --- a/docs/beam-templates/employee3out-1.hs +++ b/docs/beam-templates/employee3out-1.hs @@ -2,9 +2,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonout.hs -- ! FORMAT: console diff --git a/docs/beam-templates/employee3out-2.hs b/docs/beam-templates/employee3out-2.hs index 8a9a3483b..664f2fe82 100644 --- a/docs/beam-templates/employee3out-2.hs +++ b/docs/beam-templates/employee3out-2.hs @@ -2,9 +2,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonout.hs -- ! FORMAT: console diff --git a/docs/beam-templates/employee3out.hs b/docs/beam-templates/employee3out.hs index 47cef9d2a..a880af076 100644 --- a/docs/beam-templates/employee3out.hs +++ b/docs/beam-templates/employee3out.hs @@ -1,10 +1,11 @@ -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE NoMonomorphismRestriction #-} -{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ImpredicativeTypes #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NoMonomorphismRestriction #-} +{-# LANGUAGE TemplateHaskell #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonout.hs -- ! FORMAT: console diff --git a/docs/beam-templates/employee3sql-1.hs b/docs/beam-templates/employee3sql-1.hs index aeca6c213..96ab875ba 100644 --- a/docs/beam-templates/employee3sql-1.hs +++ b/docs/beam-templates/employee3sql-1.hs @@ -2,9 +2,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonsql.hs -- ! FORMAT: sql diff --git a/docs/beam-templates/employee3sql-2.hs b/docs/beam-templates/employee3sql-2.hs index 892b3fd96..4207d3953 100644 --- a/docs/beam-templates/employee3sql-2.hs +++ b/docs/beam-templates/employee3sql-2.hs @@ -2,9 +2,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonsql.hs -- ! FORMAT: sql diff --git a/docs/beam-templates/employee3sql.hs b/docs/beam-templates/employee3sql.hs index 85082f5c2..da4d356a5 100644 --- a/docs/beam-templates/employee3sql.hs +++ b/docs/beam-templates/employee3sql.hs @@ -1,10 +1,11 @@ {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE CPP #-} --- ! BUILD_COMMAND: runhaskell --ghc-arg=-fglasgow-exts -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures +-- ! BUILD_COMMAND: runhaskell -package microlens-th -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XGADTs -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XPartialTypeSignatures -I../../docs/beam-templates -fno-warn-partial-type-signatures -- ! BUILD_DIR: beam-sqlite/examples/ -- ! EXTRA_DEPS: employee3common.hs employee3commonsql.hs -- ! FORMAT: sql @@ -13,10 +14,11 @@ module Main where #include "employee3common.hs" #include "employee3commonsql.hs" - (do let putStrLn :: String -> IO () - putStrLn _ = pure () + (do + let putStrLn :: String -> IO () + putStrLn _ = pure () - print _ = pure () + print _ = pure () - BEAM_PLACEHOLDER + BEAM_PLACEHOLDER ) diff --git a/docs/markdown/beam_query.py b/docs/markdown/beam_query.py index 09f61267a..bb6c3d2f6 100644 --- a/docs/markdown/beam_query.py +++ b/docs/markdown/beam_query.py @@ -1,129 +1,123 @@ from markdown.preprocessors import Preprocessor from markdown.extensions import Extension -from markdown.blockprocessors import BlockProcessor import subprocess - import sqlparse - import hashlib - import os import os.path - import yaml import json - import urllib.request import zipfile - import sys + def check_ci(): - return os.environ.get("CI", 'false') == 'true' and 'BEAM_DOC_BACKEND' in os.environ + return os.environ.get("CI", "false") == "true" and "BEAM_DOC_BACKEND" in os.environ + def fetch_backend_src(backend_name, cache_dir, base_dir, src): - if 'file' in src: - return (os.path.join(base_dir, src['file']), {}) - elif 'local' in src: - path = os.path.join(base_dir, src['local']) - return (path, { 'STACK_YAML': os.path.join(path, 'stack.yaml') }) - elif 'github' in src: - repo_name = '/'.join(src['github'].split('/')[1:]) - github_archive_name = repo_name + '-' + src['revision'] - backend_dir = os.path.join(cache_dir, 'backends', backend_name + '-' + src['revision'], github_archive_name) - backend_stack_yaml = os.path.join(cache_dir, 'backends', backend_name + '-' + src['revision'], 'stack.yaml') + if "file" in src: + return os.path.join(base_dir, src["file"]) + elif "local" in src: + return os.path.join(base_dir, src["local"]) + elif "github" in src: + repo_name = "/".join(src["github"].split("/")[1:]) + github_archive_name = f"{repo_name}-{src['revision']}" + backend_dir = os.path.join( + cache_dir, "backends", f"{backend_name}-{src['revision']}", github_archive_name + ) + if not os.path.exists(backend_dir): - github_url = "https://github.com/%s/archive/%s.zip" % (src['github'], src.get('revision', 'master')) - print("Downloading beam backend", backend_name, "from", github_url) + github_url = f"https://github.com/{src['github']}/archive/{src.get('revision', 'master')}.zip" + print(f"Downloading beam backend {backend_name} from {github_url}") - local_file = os.path.join(cache_dir, backend_name + "_github.zip") + local_file = os.path.join(cache_dir, f"{backend_name}_github.zip") urllib.request.urlretrieve(github_url, local_file) print("Verifying archive...") h = hashlib.sha256() - with open(local_file, 'rb') as f: - for chunk in iter(lambda: f.read(4096 * 4), ''): + with open(local_file, "rb") as f: + for chunk in iter(lambda: f.read(4096 * 4), b""): h.update(chunk) - assert h.hexdigest() == src['sha256'], "Invalid checksum, expected %s, got %s" % (src['sha256'], h.hexdigest()) + assert h.hexdigest() == src["sha256"], ( + f"Invalid checksum, expected {src['sha256']}, got {h.hexdigest()}" + ) archive = zipfile.ZipFile(local_file) os.makedirs(backend_dir) - archive.extractall(os.path.dirname(backend_dir)) - if not os.path.exists(backend_stack_yaml): - with open(os.path.join(base_dir, 'stack.yaml')) as stack_yaml: - stack_data = yaml.load(stack_yaml) - with open(backend_stack_yaml, 'wt') as f: - stack_data['packages'] = [ os.path.join(base_dir, 'beam-core'), - os.path.join(base_dir, 'beam-migrate'), - backend_dir ] - yaml.dump(stack_data, f) - - return (backend_dir, { 'STACK_YAML': backend_stack_yaml }) + return backend_dir else: print("Invalid source spec", src) sys.exit(1) + def setup_backend(cache_dir, base_dir, backend): - src = backend['src'] - (src_dir, stack_env) = fetch_backend_src(backend['backend-name'], cache_dir, base_dir, src) - if 'STACK_IN_NIX_SHELL' in stack_env: - del stack_env['STACK_IN_NIX_SHELL'] + src = backend["src"] + src_dir = fetch_backend_src(backend["backend-name"], cache_dir, base_dir, src) - backend_script = os.path.join(src_dir, 'beam-docs.sh') - backend_cmd = 'sh %s' % backend_script + backend_script = os.path.join(src_dir, "beam-docs.sh") + backend_cmd = f"sh {backend_script}" - opts = backend.get('backend-options', '') + opts = backend.get("backend-options", "") - opts_hash = hashlib.md5(opts.encode('utf-8')).hexdigest() - status_file = backend['backend-name'] + '-' + opts_hash - status_file = os.path.join(cache_dir, status_file) + opts_hash = hashlib.md5(opts.encode("utf-8")).hexdigest() + status_file = os.path.join(cache_dir, f"{backend['backend-name']}-{opts_hash}") if not os.path.exists(status_file): - # Set up the database - print("bash environment is", - ['env', 'bash', '-c', backend_cmd + ' ' + opts], - os.path.join(base_dir, 'docs/beam-docs-library.sh')) - setup_cmd = subprocess.Popen(['env', 'bash', '-c', - backend_cmd + ' ' + opts], - cwd=os.path.abspath(cache_dir), close_fds=True, - stdout=subprocess.PIPE, - env=dict(os.environ, BEAM_DOCS_LIBRARY=os.path.join(base_dir, 'docs/beam-docs-library.sh'))) + print( + "bash environment is", + ["env", "bash", "-c", f"{backend_cmd} {opts}"], + os.path.join(base_dir, "docs/beam-docs-library.sh"), + ) + setup_cmd = subprocess.Popen( + ["env", "bash", "-c", f"{backend_cmd} {opts}"], + cwd=os.path.abspath(cache_dir), + close_fds=True, + stdout=subprocess.PIPE, + env=dict( + os.environ, + BEAM_DOCS_LIBRARY=os.path.join(base_dir, "docs/beam-docs-library.sh"), + ), + ) (out, _) = setup_cmd.communicate() retcode = setup_cmd.wait() if retcode == 0: - print('Successfully setup backend {}'.format(backend['backend-name'])) - out = out.decode('utf-8') - with open(status_file, 'wt') as f: + print(f"Successfully setup backend {backend['backend-name']}") + out = out.decode("utf-8") + with open(status_file, "wt") as f: f.write(out) - return (out, stack_env) + return out else: print(out) sys.exit(1) else: - with open(status_file, 'rt') as f: - return (f.read(), stack_env) + with open(status_file, "rt") as f: + return f.read() + def backend_match_reqs(backend, reqs): - backend_features = backend['supports'] + backend_features = backend["supports"] for req in reqs: if req.startswith("!on:"): not_backend = req[4:] - if not_backend == backend['backend-name']: + if not_backend == backend["backend-name"]: return False elif req.startswith("!") and req[1:] in backend_features: return False elif req.startswith("only:"): exp_backend = req[5:] - if exp_backend != backend['backend-name']: + if exp_backend != backend["backend-name"]: return False elif req not in backend_features: return False return True + def read_template(template_path, subst_vars): template_data = [] options = {} @@ -132,7 +126,7 @@ def read_template(template_path, subst_vars): line = line.rstrip() if line.startswith("-- !"): variable = line[5:] - key,value = variable.split(":") + key, value = variable.split(":") options[key.strip()] = value.strip() elif line.strip().startswith("BEAM_"): beam_var = line.strip()[5:] @@ -147,6 +141,7 @@ def read_template(template_path, subst_vars): template_data.append(line) return template_data, options + def hash_template(extra_data, template_path, extra_deps): lines_hash = hashlib.md5(extra_data) with open(template_path) as f: @@ -155,124 +150,148 @@ def hash_template(extra_data, template_path, extra_deps): for extra_dep in extra_deps: extra_dep = os.path.join(os.path.dirname(template_path), extra_dep) - lines_hash.update("EXTRA_DEP: {}".format(extra_dep).encode('utf-8')) + lines_hash.update(f"EXTRA_DEP: {extra_dep}".encode("utf-8")) with open(extra_dep) as f: for line in f: - lines_hash.update(line.encode('utf-8')) + lines_hash.update(line.encode("utf-8")) return lines_hash.hexdigest() + def find_cached_file(cache_dir, lines_hash): - if os.path.exists(os.path.join(cache_dir, lines_hash)): - with open(os.path.join(cache_dir, lines_hash)) as cached: + path = os.path.join(cache_dir, lines_hash) + if os.path.exists(path): + with open(path) as cached: return [x.rstrip() for x in cached] - else: - return None + return None + def save_cached_file(cache_dir, lines_hash, out): - with open(os.path.join(cache_dir, lines_hash), 'wt') as f: + with open(os.path.join(cache_dir, lines_hash), "wt") as f: f.write(out) + def run_backend_example(backend, template, cache_dir, base_dir, full_example_lines): - backend_haskell_names = backend['haskell-names'] - module = backend['backend-module'] - mnemonic = backend_haskell_names['mnemonic'] - with_database_debug = '%s.%s' % (mnemonic, backend_haskell_names['with-database-debug']) - select_syntax = '%s.%s' % (mnemonic, backend_haskell_names['select-syntax']) - backend_type = '%s.%s' % (mnemonic, backend_haskell_names['backend']) - backend_monad = '%s.%s' % (mnemonic, backend_haskell_names['monad']) - extra_imports = backend.get('extra-imports', []) + backend_haskell_names = backend["haskell-names"] + module = backend["backend-module"] + mnemonic = backend_haskell_names["mnemonic"] + with_database_debug = f"{mnemonic}.{backend_haskell_names['with-database-debug']}" + select_syntax = f"{mnemonic}.{backend_haskell_names['select-syntax']}" + backend_type = f"{mnemonic}.{backend_haskell_names['backend']}" + backend_monad = f"{mnemonic}.{backend_haskell_names['monad']}" + extra_imports = backend.get("extra-imports", []) example_lines = [] - for line in full_example_lines: - if line.startswith('--! import'): - extra_imports.append(line[len('--! import'):]) + if line.startswith("--! import"): + extra_imports.append(line[len("--! import"):]) else: example_lines.append(line) - # Attempt to setup backend - (open_db_data, stack_env) = setup_backend(cache_dir, base_dir, backend) - if 'STACK_IN_NIX_SHELL' in stack_env: - del stack_env['STACK_IN_NIX_SHELL'] - - template_data, options = read_template(template, - { 'PLACEHOLDER': example_lines, - 'MODULE_IMPORT': - ['import qualified %s as %s' % (module, mnemonic)] + - ['import %s' % mod for mod in extra_imports], - 'BACKEND_EXTRA': - backend.get('backend-extra', '').split('\n'), - 'OPEN_DATABASE': - open_db_data.split('\n') - }) - - packages = [ "beam-core", backend_haskell_names['package'] ] + backend.get('extra-packages', []) - packages = [ "-package %s" % pkgname for pkgname in packages ] - decl_options = options.get('BUILD_OPTIONS', '').replace("$$BEAM_SOURCE$$", base_dir) - build_options = " -XCPP -DBEAM_BACKEND=%s -DBEAM_BACKEND_MONAD=%s -DBEAM_WITH_DATABASE_DEBUG=%s " % (backend_type, backend_monad, with_database_debug) + \ - decl_options - extra_deps = options.get('EXTRA_DEPS', '').split() - output_format = options.get('OUTPUT_FORMAT', 'sql') - - lines_hash = hash_template(b"$$TEMPLATEPATH$$" + template.encode('ascii') + - u"".join(example_lines).encode('ascii', 'xmlcharrefreplace') + - json.dumps(backend).encode('utf-8'), - template, extra_deps) + open_db_data = setup_backend(cache_dir, base_dir, backend) + + template_data, options = read_template( + template, + { + "PLACEHOLDER": example_lines, + "MODULE_IMPORT": [f"import qualified {module} as {mnemonic}"] + + [f"import {mod}" for mod in extra_imports], + "BACKEND_EXTRA": backend.get("backend-extra", "").split("\n"), + "OPEN_DATABASE": open_db_data.split("\n"), + }, + ) + + packages = ["beam-core", backend_haskell_names["package"]] + backend.get("extra-packages", []) + packages = [f"-package {pkgname}" for pkgname in packages] + decl_options = options.get("BUILD_OPTIONS", "").replace("$$BEAM_SOURCE$$", base_dir) + build_options = ( + f" -XCPP -DBEAM_BACKEND={backend_type}" + f" -DBEAM_BACKEND_MONAD={backend_monad}" + f" -DBEAM_WITH_DATABASE_DEBUG={with_database_debug} " + + decl_options + ) + extra_deps = options.get("EXTRA_DEPS", "").split() + output_format = options.get("OUTPUT_FORMAT", "sql") + + lines_hash = hash_template( + b"$$TEMPLATEPATH$$" + + template.encode("ascii") + + "".join(example_lines).encode("ascii", "xmlcharrefreplace") + + json.dumps(backend).encode("utf-8"), + template, + extra_deps, + ) cached_data = find_cached_file(cache_dir, lines_hash) if cached_data is not None: return cached_data - source_file = os.path.join(os.path.abspath(cache_dir), lines_hash + ".hs") - with open(source_file, 'wt') as source_hdl: - source_hdl.write(u"\n".join(template_data)) + source_file = os.path.join(os.path.abspath(cache_dir), f"{lines_hash}.hs") + binary_file = os.path.join(os.path.abspath(cache_dir), f"{lines_hash}") + with open(source_file, "wt") as source_hdl: + source_hdl.write("\n".join(template_data)) - build_command = 'runhaskell ' + build_options + ' ' + source_file - print("Running backend example", lines_hash, ":", build_command) - print("With environment", stack_env) + compile_command = f"runhaskell {build_options} {source_file}" + print(f"Running backend example {lines_hash}: {compile_command}") is_ci = check_ci() - proc = subprocess.Popen(build_command, shell=True, cwd=os.path.abspath(cache_dir), close_fds=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE if not is_ci else None, - env=dict(os.environ, **stack_env)) + proc = subprocess.Popen( + compile_command, + shell=True, + cwd=os.path.abspath(cache_dir), + close_fds=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE if not is_ci else None, + ) (out, err) = proc.communicate() - out = out.decode('utf-8') + out = out.decode("utf-8") retcode = proc.wait() - print("Ran backend example", lines_hash, file=sys.stderr) + print(f"Ran backend example {lines_hash}", file=sys.stderr) if retcode == 0: - # Success!! os.remove(source_file) - - if output_format == 'sql': + # Clean up compilation artifacts + for ext in ["", ".hi", ".o"]: + path = binary_file + ext if ext else binary_file + if os.path.exists(path): + os.remove(path) + if output_format == "sql": out = sqlparse.format(out, reindent=True) - save_cached_file(cache_dir, lines_hash, out) return out.split("\n") else: if is_ci: - print("Error in source file", source_file) + print(f"Error in source file {source_file}") print("Example is\n", "\n".join(example_lines)) + print("--- Generated source ---") + with open(source_file) as dbg: + for i, line in enumerate(dbg, 1): + print(f"{i:4d} | {line}", end="") + print("--- End generated source ---") sys.exit(1) else: - print("Error in source file", source_file) + print(f"Error in source file {source_file}") sys.stderr.flush() sys.stderr.buffer.write(err) err = err.decode("utf-8") return err.split() -def run_example(template_path, cache_dir, example_lines): - template_data, options = read_template(template_path, { 'PLACEHOLDER': example_lines }) - - build_dir = options.get('BUILD_DIR', '.') - build_command = options.get('BUILD_COMMAND') - extra_deps = options.get('EXTRA_DEPS', "").split() - out_format = options.get('FORMAT', 'sql') - lines_hash = hash_template(b"$$TEMPLATEPATH$$" + template_path.encode('utf-8') + - u"".join(example_lines).encode('ascii', 'xmlcharrefreplace'), - template_path, extra_deps) +def run_example(template_path, cache_dir, example_lines): + template_data, options = read_template(template_path, {"PLACEHOLDER": example_lines}) + + build_dir = options.get("BUILD_DIR", ".") + build_command = options.get("BUILD_COMMAND") + extra_deps = options.get("EXTRA_DEPS", "").split() + out_format = options.get("FORMAT", "sql") + + lines_hash = hash_template( + b"$$TEMPLATEPATH$$" + + template_path.encode("utf-8") + + "".join(example_lines).encode("ascii", "xmlcharrefreplace"), + template_path, + extra_deps, + ) cached_data = find_cached_file(cache_dir, lines_hash) if cached_data is not None: return cached_data @@ -280,27 +299,64 @@ def run_example(template_path, cache_dir, example_lines): if build_command is None: return ["No BUILD_COMMAND specified"] + example_lines - print("Running example", lines_hash) + source_content = "\n".join(template_data) + needs_th = "TemplateHaskell" in source_content or "makeLenses" in source_content + + source_file = os.path.join(os.path.abspath(cache_dir), f"{lines_hash}.hs") + binary_file = os.path.join(os.path.abspath(cache_dir), f"{lines_hash}") + with open(source_file, "wt") as f: + f.write(source_content) + + # Note: `runhaskell` is much faster than `ghc --make`, BUT does not support + # template-haskell. Therefore, if we detect that there's some template Haskell + # in a source file (`needs_th`), we switch the build command to a much slower method. + if build_command.startswith("runhaskell"): + ghc_args = build_command[len("runhaskell"):] + if needs_th: + compile_command = f"ghc --make -O0 -o {binary_file} {ghc_args} {source_file} && {binary_file}" + else: + compile_command = f"runhaskell {ghc_args} {source_file}" + else: + compile_command = f"{build_command} {source_file}" + + print(f"Running example {lines_hash}: {compile_command}") is_ci = check_ci() - proc = subprocess.Popen(build_command, shell=True, cwd=os.path.abspath(build_dir), close_fds=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE if not is_ci else None) + proc = subprocess.Popen( + compile_command, + shell=True, + cwd=os.path.abspath(build_dir), + close_fds=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE if not is_ci else None, + ) - (out, err) = proc.communicate(u"\n".join(template_data).encode('utf-8')) - out = out.decode('utf-8') + (out, err) = proc.communicate() + out = out.decode("utf-8") retcode = proc.wait() - print("Ran example", lines_hash, file=sys.stderr) + print(f"Ran example {lines_hash}", file=sys.stderr) if retcode == 0: - if out_format == 'sql': + os.remove(source_file) + # Clean up compilation artifacts + for ext in ["", ".hi", ".o"]: + path = binary_file + ext if ext else binary_file + if os.path.exists(path): + os.remove(path) + if out_format == "sql": out = sqlparse.format(out, reindent=True) save_cached_file(cache_dir, lines_hash, out) return out.split("\n") else: if is_ci: - print("Error processing file", lines_hash) + print(f"Error in source file {source_file}") + print(f"Error processing file {lines_hash}") print("Example is\n", "\n".join(example_lines)) + print("--- Generated source ---") + with open(source_file) as dbg: + for i, line in enumerate(dbg, 1): + print(f"{i:4d} | {line}", end="") + print("--- End generated source ---") sys.exit(1) else: sys.stdout.flush() @@ -309,21 +365,18 @@ def run_example(template_path, cache_dir, example_lines): err = err.decode("utf-8") return err.split() + class BeamQueryBlockProcessor(Preprocessor): - def __init__(self, *args, **kwargs): - self.template_dir = kwargs['template_dir'] - self.cache_dir = kwargs['cache_dir'] - self.backends = kwargs['backends'] - self.base_dir = kwargs['base_dir'] - del kwargs['template_dir'] - del kwargs['cache_dir'] - del kwargs['backends'] - del kwargs['base_dir'] + def __init__(self, md, *, template_dir, cache_dir, backends, base_dir): + self.template_dir = template_dir + self.cache_dir = cache_dir + self.backends = backends + self.base_dir = base_dir if not os.path.exists(self.cache_dir): os.makedirs(self.cache_dir) - super(BeamQueryBlockProcessor, self).__init__(*args, **kwargs) + super().__init__(md) def run(self, lines): query_mode = None @@ -338,63 +391,64 @@ def run(self, lines): def do_consume(line): if line.startswith("```"): output.append("```haskell") - output.extend([q for q in cur_query]) + output.extend(cur_query) output.append("```") if is_example: for backend_name in using_backends: backend = self.backends[backend_name] - template_path = os.path.join(self.template_dir, beam_template + ".hs") - template_data = run_backend_example(backend, template_path, self.cache_dir, self.base_dir, cur_query) - - output.append("```sql name=%s" % (backend['backend-name'])) + template_path = os.path.join(self.template_dir, f"{beam_template}.hs") + template_data = run_backend_example( + backend, template_path, self.cache_dir, self.base_dir, cur_query + ) + output.append(f"```sql name={backend['backend-name']}") output.extend(template_data) output.append("```") else: - for (template, name, syntax) in beam_templates: - template_path = os.path.join(self.template_dir, template + ".hs") + for template, name, syntax in beam_templates: + template_path = os.path.join(self.template_dir, f"{template}.hs") template_data = run_example(template_path, self.cache_dir, cur_query) - - output.append("```%s name=%s" % (syntax, name)) + output.append(f"```{syntax} name={name}") output.extend(template_data) output.append("```") - return None + return None else: cur_query.append(line) return query_mode for line in lines: - if query_mode == 'start': + if query_mode == "start": if line.startswith("```haskell"): cur_query = [] - query_mode = 'get_template' + query_mode = "get_template" beam_templates = [] else: query_mode = None - elif query_mode == 'get_template': + elif query_mode == "get_template": if line.startswith("!example "): is_example = True - tmplAndReqs = line[9:].split(" ") - tmpl = tmplAndReqs[0] - reqs = tmplAndReqs[1:] + tmpl_and_reqs = line[9:].split(" ") + tmpl = tmpl_and_reqs[0] + reqs = tmpl_and_reqs[1:] - using_backends = [] - for be in self.backends.keys(): - if backend_match_reqs(self.backends[be], reqs): - using_backends.append(be) + using_backends = [ + be + for be in self.backends + if backend_match_reqs(self.backends[be], reqs) + ] beam_template = tmpl elif line.startswith("!"): - (nm, syntax) = line[1:].split()[0:2] + nm, syntax = line[1:].split()[0:2] beam_templates.append((nm, syntax, syntax)) else: - query_mode = 'consume' + query_mode = "consume" query_mode = do_consume(line) - elif query_mode == 'consume': + elif query_mode == "consume": query_mode = do_consume(line) else: if line.startswith("!beam-query"): - query_mode = 'start' + query_mode = "start" cur_query = [] beam_templates = [] else: @@ -404,34 +458,41 @@ def do_consume(line): class BeamQueryExtension(Extension): - config = { 'template_dir' : [".", "Directory for template files"], - 'cache_dir' : ["./.beam-query-cache", "Directory for cached results"], - 'conf' : ["./beam-docs.yaml", "Config file for documentation generation"], - 'enabled_backends': [ [], 'List of enabled backends (empty list means use all)' ], - 'base_dir' : [".", "Path to beam source repo"] - } - - def extendMarkdown(self, md, md_globals): - - with open(self.getConfig('conf')) as f: - conf = yaml.load(f) - - backends = conf['backends'] + config = { + "template_dir": [".", "Directory for template files"], + "cache_dir": ["./.beam-query-cache", "Directory for cached results"], + "conf": ["./beam-docs.yaml", "Config file for documentation generation"], + "enabled_backends": [[], "List of enabled backends (empty list means use all)"], + "base_dir": [".", "Path to beam source repo"], + } + + def extendMarkdown(self, md): + with open(self.getConfig("conf")) as f: + conf = yaml.load(f, Loader=yaml.SafeLoader) + + backends = conf["backends"] is_ci = check_ci() - enabled_backends = self.getConfig('enabled_backends') if not is_ci else os.environ['BEAM_DOC_BACKEND'].split() + enabled_backends = ( + self.getConfig("enabled_backends") + if not is_ci + else os.environ["BEAM_DOC_BACKEND"].split() + ) print("Enabled backends are", enabled_backends) - if len(enabled_backends) > 0: - all_backends = backends.keys() - for backend_name in all_backends: - if backend_name not in enabled_backends: - del backends[backend_name] - - md.preprocessors.add('beam-query', - BeamQueryBlockProcessor(md, template_dir=os.path.abspath(self.getConfig('template_dir')), - cache_dir=os.path.abspath(self.getConfig('cache_dir')), - backends=backends, - base_dir=os.path.abspath(self.getConfig('base_dir'))), - "_begin") + if enabled_backends: + backends = {k: v for k, v in backends.items() if k in enabled_backends} + + md.preprocessors.register( + BeamQueryBlockProcessor( + md, + template_dir=os.path.abspath(self.getConfig("template_dir")), + cache_dir=os.path.abspath(self.getConfig("cache_dir")), + backends=backends, + base_dir=os.path.abspath(self.getConfig("base_dir")), + ), + "beam-query", + 100, + ) + def makeExtension(*args, **kwargs): - return BeamQueryExtension(*args, **kwargs) + return BeamQueryExtension(*args, **kwargs) \ No newline at end of file diff --git a/docs/markdown/markdown_fenced_code_tabs.py b/docs/markdown/markdown_fenced_code_tabs.py index 317251840..4cd1120be 100644 --- a/docs/markdown/markdown_fenced_code_tabs.py +++ b/docs/markdown/markdown_fenced_code_tabs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Fenced Code Tabs Extension for Python Markdown ========================================= @@ -13,9 +11,6 @@ License: [MIT](https://opensource.org/licenses/MIT) """ -from __future__ import absolute_import -from __future__ import unicode_literals - from markdown.extensions import Extension from markdown.preprocessors import Preprocessor @@ -37,7 +32,6 @@ )) -# Fenced Code Nav Extension class FencedCodeTabsPreprocessor(Preprocessor): FENCE_BLOCK_REGEX = re.compile(r''' @@ -55,11 +49,8 @@ class FencedCodeTabsPreprocessor(Preprocessor): TAB_ITEM_BODY_WRAP_ESCAPE = '
%s
' def __init__(self, md, code_fence_tabs_config=None): - # Initialize the Preprocessor self.tab_items = deque() - self.code_fence_tabs_config = code_fence_tabs_config - self.checked_for_codehilite = False self.codehilite_conf = {} @@ -67,38 +58,29 @@ def __init__(self, md, code_fence_tabs_config=None): self.tab_item_placeholder = placeholder_dict['placeholder'] self.tab_item_placeholder_regex = placeholder_dict['placeholder_regex'] - super(FencedCodeTabsPreprocessor, self).__init__(md) + super().__init__(md) def _generate_tab_item_placeholder(self): - - # used to create a unique signature current_time = time() - return { 'placeholder': self.TAB_ITEM_PLACE_HOLDER_TEMPLATE.format(current_time), - 'placeholder_regex': re.compile(self.TAB_ITEM_PLACE_HOLDER_REGEX_TEMPLATE.format(current_time)) + 'placeholder_regex': re.compile( + self.TAB_ITEM_PLACE_HOLDER_REGEX_TEMPLATE.format(current_time) + ), } @staticmethod def _filter_content(content): - - string_block = content.replace(u'\u2018', '‘') - string_block = string_block.replace(u'\u2019', '’') - string_block = string_block.replace(u'\u201c', '“') - string_block = string_block.replace(u'\u201d', '”') - string_block = string_block.replace(u'\u2013', '–') - string_block = string_block.replace(u'\xa0', '') - - try: - string_block = string_block.decode('ascii', 'remove') - except: - string_block = content - + string_block = content.replace('\u2018', '‘') + string_block = string_block.replace('\u2019', '’') + string_block = string_block.replace('\u201c', '“') + string_block = string_block.replace('\u201d', '”') + string_block = string_block.replace('\u2013', '–') + string_block = string_block.replace('\xa0', '') return string_block @staticmethod def _escape(txt): - # HTML-entity-ize common characters txt = txt.replace('&', '&') txt = txt.replace('<', '<') txt = txt.replace('>', '>') @@ -106,67 +88,57 @@ def _escape(txt): return txt def _identify_code_tabs(self, block_str): - - text = FencedCodeTabsPreprocessor._filter_content(block_str) + text = self._filter_content(block_str) while True: m = self.FENCE_BLOCK_REGEX.search(text) - if m: - - first_line = text[m.start():].split('\n')[0] - - kwargs = {} - for param, regex in PARAM_REGEXES.items(): - param_m = regex.search(first_line) - if param_m: - if param_m.group(param): - kwargs[param] = param_m.group(param) - elif (not param_m.group(param) is None) and param in PARAM_DEFAULTS: - kwargs[param] = PARAM_DEFAULTS[param] - else: - raise Exception("{} needs an argument within \n{}".format(param, first_line)) - - lang = '' - - if m.group('lang') and m.group('lang') not in PARAM_REGEXES: - lang = m.group('lang') - - name = lang - if m.group('name'): - name = m.group('name') - - if self.codehilite_conf: - highliter = CodeHilite( - m.group('code'), - linenums=self.codehilite_conf['linenums'][0], - guess_lang=self.codehilite_conf['guess_lang'][0], - css_class=self.codehilite_conf['css_class'][0], - style=self.codehilite_conf['pygments_style'][0], - lang=(m.group('lang') or None), - noclasses=self.codehilite_conf['noclasses'][0], - hl_lines=parse_hl_lines(kwargs.get('hl_lines')) - ) - - code = highliter.hilite() - else: - code = self.TAB_ITEM_BODY_WRAP_ESCAPE % (lang, - self._escape(m.group('code'))) + if not m: + break - self.tab_items.append(FencedCodeTabs(name.title(), lang, code)) + first_line = text[m.start():].split('\n')[0] - placeholder = self.tab_item_placeholder.format(len(self.tab_items) - 1) + kwargs = {} + for param, regex in PARAM_REGEXES.items(): + param_m = regex.search(first_line) + if param_m: + if param_m.group(param): + kwargs[param] = param_m.group(param) + else: + raise Exception(f"{param} needs an argument within \n{first_line}") + + lang = '' + if m.group('lang') and m.group('lang') not in PARAM_REGEXES: + lang = m.group('lang') + + name = lang + if m.group('name'): + name = m.group('name') + + if self.codehilite_conf: + highliter = CodeHilite( + m.group('code'), + linenums=self.codehilite_conf['linenums'][0], + guess_lang=self.codehilite_conf['guess_lang'][0], + css_class=self.codehilite_conf['css_class'][0], + style=self.codehilite_conf['pygments_style'][0], + lang=(m.group('lang') or None), + noclasses=self.codehilite_conf['noclasses'][0], + hl_lines=parse_hl_lines(kwargs.get('hl_lines')), + ) + code = highliter.hilite() + else: + code = self.TAB_ITEM_BODY_WRAP_ESCAPE % ( + lang, self._escape(m.group('code')) + ) - text = "{}\n{}\n{}".format(text[:m.start()], - placeholder, - text[m.end():]) + self.tab_items.append(FencedCodeTabs(name.title(), lang, code)) - else: - break + placeholder = self.tab_item_placeholder.format(len(self.tab_items) - 1) + text = f"{text[:m.start()]}\n{placeholder}\n{text[m.end():]}" return text def _populate_tabs(self, text): - lines = text.split('\n') start_tab_index = None tab_run_length = 0 @@ -180,87 +152,72 @@ def _populate_tabs(self, text): if m: if start_tab_index is None: start_tab_index = m.group(1) - tab_run_length += 1 - - # Ignore the remainder of the loop continue - else: - - # We have a non tab save to the tab set so let's aggregate - # the tabs into a tab set and generate the corresponding HTML - if len(line.strip()) != 0 and start_tab_index is not None: - if single_block_as_tab or tab_run_length > 1: - tab_set = FencedCodeTabsSet('tab-' + str(tab_set_count) + '-') - tab_set_count += 1 + if len(line.strip()) != 0 and start_tab_index is not None: + if single_block_as_tab or tab_run_length > 1: + tab_set = FencedCodeTabsSet(f'tab-{tab_set_count}-') + tab_set_count += 1 - for i in range(0, tab_run_length): - tab = self.tab_items.popleft() - tab_set.add_code_tab(tab) + for _ in range(tab_run_length): + tab = self.tab_items.popleft() + tab_set.add_code_tab(tab) - # Convert our tab set (and tabs) into the appropriate HTML - tab_html = str(tab_set) - else: - # Convert our single tab into the appropriate HTML - tab_html = str(self.tab_items.popleft()) + tab_html = str(tab_set) + else: + tab_html = str(self.tab_items.popleft()) - start_tab_index = None - tab_run_length = 0 - transformed_lines += '\n' + self.markdown.htmlStash.store(tab_html) + '\n\n' + start_tab_index = None + tab_run_length = 0 + transformed_lines += '\n' + self.md.htmlStash.store(tab_html) + '\n\n' - transformed_lines += line + '\n' + transformed_lines += line + '\n' - # If there are any remaining tabs enclose them in a final last tab set if len(self.tab_items) > 0: tab_html = str(self.tab_items[0]) if single_block_as_tab or len(self.tab_items) > 1: - tab_set = FencedCodeTabsSet('tab-' + str(num_tabs) + '-') - + tab_set = FencedCodeTabsSet(f'tab-{num_tabs}-') for tab in self.tab_items: tab_set.add_code_tab(tab) - tab_html = str(tab_set) - transformed_lines += '\n\n' + self.markdown.htmlStash.store(str(tab_html)) + '\n\n' + transformed_lines += '\n\n' + self.md.htmlStash.store(str(tab_html)) + '\n\n' self.tab_items.clear() return transformed_lines def run(self, lines): - # Check for code hilite extension if not self.checked_for_codehilite: - for ext in self.markdown.registeredExtensions: + for ext in self.md.registeredExtensions: if isinstance(ext, CodeHiliteExtension): self.codehilite_conf = ext.config break - self.checked_for_codehilite = True text = self._identify_code_tabs('\n'.join(lines)) return self._populate_tabs(text).split('\n') -# Fenced Code Nav -class FencedCodeTabsSet(object): +class FencedCodeTabsSet: - TAB_SET_HANDLE_CONTAINER_TEMPLATE = u""" + TAB_SET_HANDLE_CONTAINER_TEMPLATE = """ """ - TAB_SET_HANDLE_TEMPLATE = u""" + TAB_SET_HANDLE_TEMPLATE = """ """ - TAB_SET_TAB_CONTAINER_TEMPLATE = u""" + TAB_SET_TAB_CONTAINER_TEMPLATE = """
{tabs}
""" - TAB_BODY_CONTAINER_TEMPLATE = u""" + TAB_BODY_CONTAINER_TEMPLATE = """
{tabContent}
@@ -278,20 +235,17 @@ def get_code_tabs(self): return self.codeTabs def _get_tab_id(self, tab): - tab_name = tab.get_name() tab_id = tab_name if tab_name is None or not tab_name.strip(): - tab_id = ''. join( + tab_id = ''.join( random.SystemRandom().choice( string.ascii_lowercase + string.digits ) for _ in range(self.RANDOM_ID_CHAR_LENGTH) ) - tab_set_id = self.id + tab_id - - return tab_set_id + return self.id + tab_id def __str__(self): tab_active_class = 'active' @@ -299,36 +253,37 @@ def __str__(self): tabs = '' for tab in self.codeTabs: - tab_set_id = self._get_tab_id(tab) lang = tab.get_lang() - tab_handles += self.TAB_SET_HANDLE_TEMPLATE.format(id=tab_set_id, - isTabActiveClass=tab_active_class, - lang=lang, - ulang=tab.get_name()) - - tabs += self.TAB_BODY_CONTAINER_TEMPLATE.format(id=tab_set_id, - isTabActiveClass=tab_active_class, - lang=tab.get_lang(), - tabContent=tab) + tab_handles += self.TAB_SET_HANDLE_TEMPLATE.format( + id=tab_set_id, + isTabActiveClass=tab_active_class, + lang=lang, + ulang=tab.get_name(), + ) + tabs += self.TAB_BODY_CONTAINER_TEMPLATE.format( + id=tab_set_id, + isTabActiveClass=tab_active_class, + lang=tab.get_lang(), + tabContent=tab, + ) tab_active_class = '' tab_set_str = self.TAB_SET_HANDLE_CONTAINER_TEMPLATE.format(tabHandles=tab_handles) tab_set_str += self.TAB_SET_TAB_CONTAINER_TEMPLATE.format(tabs=tabs) - return """ + return f"""
- {tabSet} + {tab_set_str}
- """.format(tabSet=tab_set_str) + """ def __repr__(self): return self.__str__() -# Fenced Code Nav Item -class FencedCodeTabs(object): +class FencedCodeTabs: def __init__(self, name, lang, body): self.name = name @@ -348,40 +303,33 @@ def __repr__(self): return self.__str__() -# Extension Class class FencedCodeTabsExtension(Extension): def __init__(self, *args, **kwargs): - - # Config defaults self.config = { 'single_block_as_tab': [False, 'Render a single ``` code block as a tab'], } - - super(FencedCodeTabsExtension, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @staticmethod def to_bool(param): - the_bool = param - if isinstance(param, str): - the_bool = False if param.lower() is 'false' else True - - return the_bool - - def extendMarkdown(self, md, md_globals): + return param.lower() != 'false' + return param - # Just in case convert this parameter to a bool if it is sent in as a string - self.setConfig('single_block_as_tab', FencedCodeTabsExtension.to_bool( - self.getConfig('single_block_as_tab') - )) + def extendMarkdown(self, md): + self.setConfig( + 'single_block_as_tab', + self.to_bool(self.getConfig('single_block_as_tab')), + ) md.registerExtension(self) - # Add FencedCodeTabsPreprocessor to the Markdown instance. - md.preprocessors.add('fenced_code_block', - FencedCodeTabsPreprocessor(md, self.getConfigs()), - '>normalize_whitespace') + md.preprocessors.register( + FencedCodeTabsPreprocessor(md, self.getConfigs()), + 'fenced_code_block', + 27, + ) def makeExtension(*args, **kwargs): diff --git a/docs/poetry.lock b/docs/poetry.lock deleted file mode 100644 index 06357ab92..000000000 --- a/docs/poetry.lock +++ /dev/null @@ -1,448 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "click" -version = "8.0.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] - -[[package]] -name = "ghp-import" -version = "2.0.2" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"}, - {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "importlib-metadata" -version = "4.10.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "importlib_metadata-4.10.0-py3-none-any.whl", hash = "sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4"}, - {file = "importlib_metadata-4.10.0.tar.gz", hash = "sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "jinja2" -version = "3.0.1" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.3.4" -description = "Python implementation of Markdown." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, - {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, -] - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.0.1" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.2.3" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"}, - {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, -] - -[package.dependencies] -click = ">=3.3" -ghp-import = ">=1.0" -importlib-metadata = ">=3.10" -Jinja2 = ">=2.10.1" -Markdown = ">=3.2.1" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -PyYAML = ">=3.10" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] - -[[package]] -name = "mkdocs-material" -version = "5.0.2" -description = "A Material Design theme for MkDocs" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "mkdocs-material-5.0.2.tar.gz", hash = "sha256:30f78994f79132453bb30cdf79fc8e392f908d1058b509036ac284339ed7e8e3"}, - {file = "mkdocs_material-5.0.2-py2.py3-none-any.whl", hash = "sha256:304ce9e489074588376d921373010db964b5d062747d80bf61753156350c817c"}, -] - -[package.dependencies] -markdown = ">=3.2" -mkdocs = ">=1.1" -Pygments = ">=2.4" -pymdown-extensions = ">=7.0" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pygments" -version = "2.9.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, - {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, -] - -[[package]] -name = "pymdown-extensions" -version = "10.0" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "pymdown_extensions-10.0-py3-none-any.whl", hash = "sha256:e6cbe8ace7d8feda30bc4fd6a21a073893a9a0e90c373e92d69ce5b653051f55"}, - {file = "pymdown_extensions-10.0.tar.gz", hash = "sha256:9a77955e63528c2ee98073a1fb3207c1a45607bc74a34ef21acd098f46c3aa8a"}, -] - -[package.dependencies] -markdown = ">=3.2" -pyyaml = "*" - -[[package]] -name = "pyparsing" -version = "3.0.6" -description = "Python parsing module" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -groups = ["main"] -files = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sqlparse" -version = "0.5.4" -description = "A non-validating SQL parser." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"}, - {file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"}, -] - -[package.extras] -dev = ["build"] -doc = ["sphinx"] - -[[package]] -name = "watchdog" -version = "2.1.6" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, - {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, - {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, - {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, - {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, - {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, - {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, - {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, - {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, - {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "zipp" -version = "3.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, -] - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy ; platform_python_implementation != \"PyPy\""] - -[metadata] -lock-version = "2.1" -python-versions = "^3.8" -content-hash = "7c83536d3e32b7b0c265e8c6786b015af47eb2e6e99255b1486f07ef54f0f15e" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 69a8be0f3..f9682f34a 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -5,8 +5,8 @@ description = "MkDocs project for Beam" authors = [] [tool.poetry.dependencies] -python = "^3.8" -mkdocs = "1.2.3" # later versions fail to build in poetry2nix derivation +python = "^3.12" +mkdocs = "1.6" # later versions fail to build in poetry2nix derivation sqlparse = "^0.5.4" -PyYAML = "^5.4.1" -mkdocs-material = "5.0.2" # later versions cause infinite recursion in poetry2nix +PyYAML = "^6.0" +mkdocs-material = "^9.5" # later versions cause infinite recursion in poetry2nix diff --git a/docs/tutorials/tutorial2.md b/docs/tutorials/tutorial2.md index 19ea96227..af565da60 100644 --- a/docs/tutorials/tutorial2.md +++ b/docs/tutorials/tutorial2.md @@ -202,91 +202,17 @@ Easier queries with lenses In the previous part, we accessed table columns by using regular Haskell record syntax. Sometimes, we would like to use the more convenient lens syntax to access columns. Of course, all of beam's definitions are compatible with the -`lens` library -- that is to say, `makeLenses` will work just fine. However, -beam's motivation is, in part, the avoidance of Template Haskell, and it would -hardly be worth it if you had to include a Template Haskell splice just to have -lenses for the models you declared TH free. - -In reality, the `lens` library isn't required to construct valid lenses. Lenses -are a plain old Haskell type. - -We can use beam's `Columnar` mechanism to automatically derive lenses. The -`tableLenses` function produces a table value where each column is given a type -`LensFor`, which is a `newtype` wrapper over a correctly constructed, -polymorphic Van Laarhoven lens. - -We can bring these lenses into scope globally via a global pattern match against -`tableLenses`. For example, we get lenses for each column of the `AddressT` and -`UserT` table below. +`lens` library -- that is to say, `makeLenses` will work just fine. Since this isn't +an optics tutorial, we'll go ahead and use `microlens-th`'s `makeLenses`: ```haskell --- Add the following to the top of the file, for GHC >8.2 -{-# LANGUAGE ImpredicativeTypes #-} - -Address (LensFor addressId) _ _ _ _ _ _ = tableLenses -Address _ (LensFor addressLine1) _ _ _ _ _ = tableLenses -Address _ _ (LensFor addressLine2) _ _ _ _ = tableLenses -Address _ _ _ (LensFor addressCity) _ _ _ = tableLenses -Address _ _ _ _ (LensFor addressState) _ _ = tableLenses -Address _ _ _ _ _ (LensFor addressZip) _ = tableLenses -Address _ _ _ _ _ _ (UserId (LensFor addressForUserId)) = tableLenses - -User (LensFor userEmail) _ _ _ = tableLenses -User _ (LensFor userFirstName) _ _ = tableLenses -User _ _ (LensFor userLastName) _ = tableLenses -User _ _ _ (LensFor userPassword) = tableLenses -``` - -!!! note "Note" - The `ImpredicativeTypes` language extension is necessary for newer - GHC to allow the polymorphically typed lenses to be introduced at - the top-level. Older GHCs were more lenient. As for why we must - create a separate pattern match for each lens we would like to - generate, please refer to GitHub issues [#659](https://github.com/haskell-beam/beam/issues/659) and [#664](https://github.com/haskell-beam/beam/issues/664). - -As in tables, we can generate lenses for databases via the `dbLenses` function. +{-# LANGUAGE TemplateHaskell #-} -```haskell -ShoppingCartDb (TableLens shoppingCartUsers) - (TableLens shoppingCartUserAddresses) = - dbLenses +makeLenses ''AddressT +makeLenses ''UserT +makeLenses ''ShoppingCartDb ``` -We can ask GHCi for the type of a column lens. - -``` -Prelude Database.Beam Database.Beam.Sqlite Data.Text Database.SQLite.Simple> :t addressId -addressId - :: Functor f2 => - (Columnar f1 Int32 -> f2 (Columnar f1 Int32)) - -> AddressT f1 -> f2 (AddressT f1) -``` - -This lens is compatible with those of the `lens` library. - -And a table lens, for good measure - -``` -Prelude Database.Beam Database.Beam.Sqlite Data.Text Database.SQLite.Simple> :t shoppingCartUsers -shoppingCartUsers - :: Functor f1 => - (f2 (TableEntity UserT) -> f1 (f2 (TableEntity UserT))) - -> ShoppingCartDb f2 -> f1 (ShoppingCartDb f2) -``` - -!!! warning "Warning" - These lens generating functions are *awesome* but if you use them in a - compiled Haskell module (rather than GHC), GHC may give you odd compile - errors about ambiguous types. These occur due to what's known as the - monomorphism restriction. You can turn it off using the - `NoMonomorphismRestriction` extension. - - The monomorphism restriction is part of the Haskell standard, but there has - been talk about removing it in future language versions. Basically, it - requires GHC to not automatically infer polymorphic types for global - definitions. In this case though, polymorphic global definitions is exactly - what we want. - Working with relations ======== @@ -455,7 +381,7 @@ usersAndRelatedAddresses <- runSelectReturningList $ select $ do user <- all_ (shoppingCartDb ^. shoppingCartUsers) address <- all_ (shoppingCartDb ^. shoppingCartUserAddresses) - guard_ (address ^. addressForUserId ==. user ^. userEmail) + guard_ (address ^. (addressForUser . userId) ==. user ^. userEmail) pure (user, address) mapM_ print usersAndRelatedAddresses @@ -559,7 +485,7 @@ update the corresponding record in the database. [james] <- runBeamSqliteDebug putStrLn conn $ do runUpdate $ - save (shoppingCartDb ^. shoppingCartUsers) (james { _userPassword = "52a516ca6df436828d9c0d26e31ef704" }) + save (shoppingCartDb ^. shoppingCartUsers) (james & userPassword .~ "52a516ca6df436828d9c0d26e31ef704") runSelectReturningList $ lookup_ (shoppingCartDb ^. shoppingCartUsers) (UserId "james@example.com") @@ -625,8 +551,7 @@ Conclusion In this tutorial we created our first beam relationship. We saw how to use the modifications system to override the default names given to database entities. -We saw how to use `tableLenses` to generate lenses that can be used with any -lens library. We used the monadic query interface to write queries that used SQL +We used the monadic query interface to write queries that used SQL joins, and we saw how beam makes it easy to automatically pull related tables into our queries. Finally we introduced the `runUpdate` and `runDelete` functions and demonstrated several ways to construct UPDATEs and DELETEs. diff --git a/docs/tutorials/tutorial3.md b/docs/tutorials/tutorial3.md index 907612b28..aa1acb95c 100644 --- a/docs/tutorials/tutorial3.md +++ b/docs/tutorials/tutorial3.md @@ -117,13 +117,8 @@ instance Table LineItemT where Now we'll add all these tables to our database. ```haskell --- Some convenience lenses - -LineItem _ _ (LensFor lineItemQuantity) = tableLenses -Product (LensFor productId) _ _ _ = tableLenses -Product _ (LensFor productTitle) _ _ = tableLenses -Product _ _ (LensFor productDescription) _ = tableLenses -Product _ _ _ (LensFor productPrice) = tableLenses +makeLenses ''LineItemT +makeLenses ''ProductT data ShoppingCartDb f = ShoppingCartDb { _shoppingCartUsers :: f (TableEntity UserT) @@ -134,12 +129,7 @@ data ShoppingCartDb f = ShoppingCartDb , _shoppingCartLineItems :: f (TableEntity LineItemT) } deriving (Generic, Database be) -ShoppingCartDb (TableLens shoppingCartUsers) _ _ _ _ _ = dbLenses -ShoppingCartDb _ (TableLens shoppingCartUserAddresses) _ _ _ _ = dbLenses -ShoppingCartDb _ _ (TableLens shoppingCartProducts) _ _ _ = dbLenses -ShoppingCartDb _ _ _ (TableLens shoppingCartOrders) _ _ = dbLenses -ShoppingCartDb _ _ _ _ (TableLens shoppingCartShippingInfos) _ = dbLenses -ShoppingCartDb _ _ _ _ _ (TableLens shoppingCartLineItems) = dbLenses +makeLenses ''ShoppingCartDb shoppingCartDb :: DatabaseSettings be ShoppingCartDb shoppingCartDb = defaultDbSettings `withDbModification` @@ -328,7 +318,8 @@ resulting rows have a timestamp set by the database. insertExpressions $ [ Order default_ currentTimestamp_ (val_ (pk james)) (val_ (pk jamesAddress1)) nothing_ , Order default_ currentTimestamp_ (val_ (pk betty)) (val_ (pk bettyAddress1)) (just_ (val_ (pk bettyShippingInfo))) - , Order default_ currentTimestamp_ (val_ (pk james)) (val_ (pk jamesAddress1)) nothing_ ] + , Order default_ currentTimestamp_ (val_ (pk james)) (val_ (pk jamesAddress1)) nothing_ + ] print jamesOrder1 print bettyOrder1 diff --git a/docs/user-guide/backends/beam-postgres.md b/docs/user-guide/backends/beam-postgres.md index d8b2e80b8..465b0fa49 100644 --- a/docs/user-guide/backends/beam-postgres.md +++ b/docs/user-guide/backends/beam-postgres.md @@ -305,26 +305,26 @@ First, we order the employees by org structure so that managers appear first, fo !beam-query ```haskell !example chinook only:Postgres -aggregate_ (\(cust, emp) -> (group_ cust, Pg.pgArrayAgg (employeeId emp))) - $ do inv <- filter_ (\i -> invoiceDate i >=. val_ (read "2024-09-01 00:00:00.000000") &&. invoiceDate i <=. val_ (read "2024-10-01 00:00:00.000000")) $ all_ (invoice chinookDb) - cust <- lookup_ (customer chinookDb) (invoiceCustomer inv) - -- Lookup all employees and their levels - (employee, _, _) <- - Pg.pgSelectWith $ do - let topLevelEmployees = - fmap (\e -> (e, val_ (via @Int32 0))) $ - filter_ (\e -> isNull_ (employeeReportsTo e)) $ all_ (employee chinookDb) - rec employeeOrgChart <- - selecting (topLevelEmployees `unionAll_` - do { (manager, managerLevel) <- reuse employeeOrgChart - ; report <- filter_ (\e -> employeeReportsTo e ==. manager) $ all_ (employee chinookDb) - ; pure (report, managerLevel + val_ 1) }) - pure $ filter_ (\(employee, level, minLevel) -> level ==. minLevel) - $ withWindow_ (\(employee, level) -> frame_ (partitionBy_ (addressCity (employeeAddress employee))) noOrder_ noBounds_) - (\(employee, level) cityFrame -> - (employee, level, coalesce_ [min_ level `over_` cityFrame] (val_ 0))) - (reuse employeeOrgChart) - -- Limit the search only to employees that live in the same city - guard_ (addressCity (employeeAddress employee) ==. addressCity (customerAddress cust)) - pure (cust, employee) +aggregate_ (\(cust, emp) -> (group_ cust, Pg.pgArrayAgg (employeeId emp))) $ do + inv <- filter_ (\i -> invoiceDate i >=. val_ (read "2024-09-01 00:00:00.000000") &&. invoiceDate i <=. val_ (read "2024-10-01 00:00:00.000000")) $ all_ (invoice chinookDb) + cust <- filter_ (\c -> pk c ==. invoiceCustomer inv) $ all_ (customer chinookDb) + -- Lookup all employees and their levels + (employee, _, _) <- + Pg.pgSelectWith $ do + let topLevelEmployees = + fmap (\e -> (e, as_ @Int32 (val_ 0))) $ + filter_ (\e -> isNothing_ (employeeReportsTo e)) $ all_ (employee chinookDb) + rec employeeOrgChart <- + selecting (topLevelEmployees `unionAll_` + do { (manager, managerLevel) <- reuse employeeOrgChart + ; report <- filter_ (\e -> employeeReportsTo e ==. just_ (pk manager)) $ all_ (employee chinookDb) + ; pure (report, managerLevel + val_ 1) }) + pure $ filter_ (\(_, level, minLevel) -> level ==. minLevel) + $ withWindow_ (\(employee, _) -> frame_ (partitionBy_ (addressCity (employeeAddress employee))) noOrder_ noBounds_) + (\(employee, level) cityFrame -> + (employee, level, coalesce_ [min_ level `over_` cityFrame] (val_ 0))) + (reuse employeeOrgChart) + -- Limit the search only to employees that live in the same city + guard_ (addressCity (employeeAddress employee) ==. addressCity (customerAddress cust)) + pure (cust, employee) ``` diff --git a/flake.lock b/flake.lock index dd3b88573..bde55ac31 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,18 @@ { "nodes": { + "duckdb-ffi": { + "flake": false, + "locked": { + "lastModified": 1000000000, + "narHash": "sha256-yzYN/zhcWa0318QWPLbP9YFrlKYEtj9tPl10mi4klcI=", + "type": "tarball", + "url": "https://hackage.haskell.org/package/duckdb-ffi-1.4.1.5/duckdb-ffi-1.4.1.5.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://hackage.haskell.org/package/duckdb-ffi-1.4.1.5/duckdb-ffi-1.4.1.5.tar.gz" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -65,6 +78,7 @@ }, "root": { "inputs": { + "duckdb-ffi": "duckdb-ffi", "flake-parts": "flake-parts", "haskell-flake": "haskell-flake", "nixpkgs": "nixpkgs", diff --git a/flake.nix b/flake.nix index 1709787ea..67e7b92b0 100644 --- a/flake.nix +++ b/flake.nix @@ -7,56 +7,151 @@ testcontainers.url = "github:testcontainers/testcontainers-hs/e286bd2ba9747c2d8c3756a3a89910e579e318e3"; testcontainers.flake = false; + + duckdb-ffi.url = "https://hackage.haskell.org/package/duckdb-ffi-1.4.1.5/duckdb-ffi-1.4.1.5.tar.gz"; + duckdb-ffi.flake = false; }; outputs = inputs@{ self, nixpkgs, flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = nixpkgs.lib.systems.flakeExposed; imports = [ inputs.haskell-flake.flakeModule ]; - perSystem = { self', pkgs, ... }: { + perSystem = { self', pkgs, config, ... }: + let + chinookPostgresRaw = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/lerocha/chinook-database/e7e6d5f008e35d3f89d8b8a4f8d38e3bfa7e34bd/ChinookDatabase/DataSources/Chinook_PostgreSql.sql"; + sha256 = "sha256-CVQAyq0WlAn7+0d72nsm9krVDLtMA1QcgHJhwdttNC4="; + }; - haskellProjects.default = { - basePackages = pkgs.haskell.packages.ghc98; + chinookPostgres = pkgs.runCommand "chinook-postgres" {} '' + ${pkgs.glibc.bin}/bin/iconv -f ISO-8859-2 -t UTF-8 ${chinookPostgresRaw} > $out + ''; - packages = { - testcontainers.source = inputs.testcontainers; - }; - settings = { - testcontainers.check = false; - beam-postgres.check = false; + chinookSqliteRaw = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/lerocha/chinook-database/e7e6d5f008e35d3f89d8b8a4f8d38e3bfa7e34bd/ChinookDatabase/DataSources/Chinook_Sqlite.sql"; + sha256 = "sha256-Zu+IP8fhmYwpgofjtMJLvL8jFRlKJ43mjLANivq6Q9s="; }; - devShell = { - enable = true; + chinookSqlite = pkgs.runCommand "chinook-sqlite" {} '' + tail -c +4 ${chinookSqliteRaw} > $out + ''; + + pythonEnv = pkgs.python312.withPackages (ps: [ + ps.mkdocs + ps.mkdocs-material + ps.sqlparse + ps.pyyaml + ps.pygments + ]); + + ghcWithBeam = config.haskellProjects.default.outputs.finalPackages.ghcWithPackages (hp: [ + hp.beam-core + hp.beam-migrate + hp.beam-postgres + hp.beam-sqlite + hp.microlens-th + ]); + + in { + + haskellProjects.default = { + basePackages = pkgs.haskell.packages.ghc910; + + packages = { + testcontainers.source = inputs.testcontainers; + duckdb-ffi.source = inputs.duckdb-ffi; + }; + settings = { + testcontainers.check = false; + beam-postgres.check = false; + duckdb-ffi.broken = false; + duckdb-ffi.check = false; + duckdb-ffi.extraLibraries = [ pkgs.duckdb ]; + }; + + devShell = { + enable = true; - hlsCheck.enable = false; + hlsCheck.enable = false; - tools = hp: { - inherit (pkgs) - postgresql - sqlite-interactive - poetry - curl - pv # http://www.ivarch.com/programs/pv.shtml - ; - }; + tools = hp: { + inherit (pkgs) + postgresql + sqlite-interactive + poetry + curl + pv + duckdb + ; + }; + }; }; - }; - packages.all = pkgs.symlinkJoin { - name = "all"; - paths = [ - self'.packages.beam-core - self'.packages.beam-migrate - self'.packages.beam-postgres - self'.packages.pagila - self'.packages.beam-sqlite - ]; - }; + packages.all = pkgs.symlinkJoin { + name = "all"; + paths = [ + self'.packages.beam-core + self'.packages.beam-migrate + self'.packages.beam-postgres + self'.packages.pagila + self'.packages.beam-sqlite + ]; + }; + + packages.default = self'.packages.all; + + packages.docs = pkgs.stdenv.mkDerivation { + pname = "beam-docs"; + version = "0"; + src = pkgs.nix-gitignore.gitignoreFilterSource + (path: type: let + prefix = "${builtins.toPath ./.}/"; + strippedPath = pkgs.lib.removePrefix prefix path; + in pkgs.lib.or + (builtins.elem strippedPath [ + "beam-postgres" + "beam-sqlite" + ]) + (builtins.any (pkgs.lib.flip pkgs.lib.hasPrefix strippedPath) [ + "build-docs.sh" + "mkdocs.yml" + "docs" + "beam-postgres/examples" + "beam-postgres/beam-docs.sh" + "beam-sqlite/examples" + "beam-sqlite/beam-docs.sh" + ]) + ) + [] + ./.; - packages.default = self'.packages.all; + nativeBuildInputs = [ + ghcWithBeam + pythonEnv + pkgs.postgresql + pkgs.sqlite + pkgs.curl + pkgs.pv + ]; - # docs = import ./docs { inherit nixpkgs; }; - }; + buildPhase = '' + mkdir postgres + export PGHOST=$(mktemp -d /tmp/pg.XXXXXX) + initdb -D postgres + echo "unix_socket_directories = '$PGHOST'" >> postgres/postgresql.conf + pg_ctl -D postgres -o "-c listen_addresses=localhost" start + trap "pg_ctl -D postgres stop -m immediate" EXIT + + mkdir -p docs/.beam-query-cache/chinook-data + cp ${chinookPostgres} docs/.beam-query-cache/chinook-data/Chinook_PostgreSql.sql + cp ${chinookSqlite} docs/.beam-query-cache/chinook-data/Chinook_Sqlite.sql + CI=true BEAM_DOC_BACKEND="beam-postgres beam-sqlite" bash ./build-docs.sh builddocs + ''; + + installPhase = '' + cp -r site $out + ''; + }; + }; }; } diff --git a/mkdocs.yml b/mkdocs.yml index 8545b0164..baf49c1d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,7 +57,9 @@ markdown_extensions: # - beam-mssql - docs.markdown.markdown_fenced_code_tabs - def_list - - codehilite + - codehilite: + css_class: highlight + guess_lang: false - footnotes extra: logo: 'img/logo.svg' From 946c6b743868f664e6b15379c3f103ff13010551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sun, 1 Mar 2026 21:34:46 -0500 Subject: [PATCH 2/2] Added support for beam-duckdb in documentation --- .github/workflows/nix-flake.yaml | 6 - beam-duckdb/CHANGELOG.md | 5 + beam-duckdb/beam-docs.sh | 27 ++++ beam-duckdb/beam-duckdb.cabal | 3 +- beam-duckdb/docs/Chinook.sql | 151 ++++++++++++++++++ beam-duckdb/src/Database/Beam/DuckDB.hs | 8 + .../src/Database/Beam/DuckDB/Backend.hs | 59 ++++--- .../src/Database/Beam/DuckDB/Syntax.hs | 14 +- build-docs.sh | 2 +- docs/beam-templates/duckdb-parquet-out.hs | 75 +++++++++ docs/beam-templates/duckdb-parquet-sql.hs | 81 ++++++++++ docs/beam.yaml | 30 ++++ docs/data/exams.parquet | Bin 0 -> 1500 bytes docs/default.nix | 74 --------- docs/index.md | 4 + docs/markdown/beam_query.py | 13 +- docs/user-guide/backends/beam-duckdb.md | 45 ++++-- docs/user-guide/expressions.md | 5 +- docs/user-guide/manipulation/delete.md | 2 +- docs/user-guide/manipulation/insert.md | 24 ++- docs/user-guide/manipulation/update.md | 13 +- docs/user-guide/queries/select.md | 2 +- flake.nix | 99 ++++++++---- mkdocs.yml | 3 +- 24 files changed, 563 insertions(+), 182 deletions(-) create mode 100644 beam-duckdb/beam-docs.sh create mode 100644 beam-duckdb/docs/Chinook.sql create mode 100644 docs/beam-templates/duckdb-parquet-out.hs create mode 100644 docs/beam-templates/duckdb-parquet-sql.hs create mode 100644 docs/data/exams.parquet delete mode 100644 docs/default.nix diff --git a/.github/workflows/nix-flake.yaml b/.github/workflows/nix-flake.yaml index 31d44c202..13c9760d2 100644 --- a/.github/workflows/nix-flake.yaml +++ b/.github/workflows/nix-flake.yaml @@ -17,12 +17,6 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main - # duckdb-ffi is marked as broken, and thus we cannot build - # beam-duckdb via nix at this time - - name: Disable beam-duckdb - run: | - # We must specify a backup extension on MacOS - sed -i.bak '/beam-duckdb/d' cabal.project && rm cabal.project.bak - name: "Check `nix develop` shell" run: nix develop --check - name: "Check `nix develop` shell can run command" diff --git a/beam-duckdb/CHANGELOG.md b/beam-duckdb/CHANGELOG.md index bb4f4b5a7..69dd9424c 100644 --- a/beam-duckdb/CHANGELOG.md +++ b/beam-duckdb/CHANGELOG.md @@ -1,5 +1,10 @@ # Revision history for beam-duckdb +## 0.1.1.0 -- unreleased + +* Fixed an issue with modeling boolean conditions, whereby, for example, checking if something was true was modeled as `... IS 1` (as for Sqlite), rather than `... IS TRUE` (like Postgres). +* Added a `FromBackendRow DuckDB Scientific`, `HasSqlEqualityCheck DuckDB Scientific`, and `HasSqlQuantifiedEqualityCheck DuckDB Scientific` instances, which are required to build the documentation. + ## 0.1.0.0 -- 2026-02-26 * First version. Released on an unsuspecting world. diff --git a/beam-duckdb/beam-docs.sh b/beam-duckdb/beam-docs.sh new file mode 100644 index 000000000..913dd8f04 --- /dev/null +++ b/beam-duckdb/beam-docs.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e + +. ${BEAM_DOCS_LIBRARY} + +DUCKDB_DB=$1 + +print_open_statement() { + echo "chinook <- open \"chinook.ddb\"" +} + +if [ -f $DUCKDB_DB ]; then + print_open_statement + exit 0 +fi + +beam_doc_status "Creating temporary $DUCKDB_DB..." + +rm -f $DUCKDB_DB.tmp +duckdb $DUCKDB_DB.tmp < chinook-data/Chinook_DuckDB.sql + +beam_doc_status "Success, creating $DUCKDB_DB" + +mv $DUCKDB_DB.tmp $DUCKDB_DB + +print_open_statement diff --git a/beam-duckdb/beam-duckdb.cabal b/beam-duckdb/beam-duckdb.cabal index 8b975122d..d3d2976c3 100644 --- a/beam-duckdb/beam-duckdb.cabal +++ b/beam-duckdb/beam-duckdb.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: beam-duckdb -version: 0.1.0.0 +version: 0.1.1.0 synopsis: DuckDB backend for Beam description: Beam driver for DuckDB, an analytics-focused open-source in-process database. license: MIT @@ -44,6 +44,7 @@ library , duckdb-simple ^>=0.1 , dlist >=0.8 && <1.1 , free >=4.12 && <5.3 + , scientific ^>=0.3 , text >=1.0 && <2.2 , time >=1.6 && <1.16 , transformers >=0.3 && <0.7 diff --git a/beam-duckdb/docs/Chinook.sql b/beam-duckdb/docs/Chinook.sql new file mode 100644 index 000000000..c1da835b1 --- /dev/null +++ b/beam-duckdb/docs/Chinook.sql @@ -0,0 +1,151 @@ +-- This script is adapted from +-- https://github.com/RandomFractals/duckdb-sql-tools/blob/main/data/chinook/duckdb/create.sql +-- to match the schema that is used for Postgres and Sqlite +drop table if exists Album; +drop table if exists Artist; +drop table if exists Customer; +drop table if exists Employee; +drop table if exists Genre; +drop table if exists Invoice; +drop table if exists InvoiceLine; +drop table if exists MediaType; +drop table if exists Playlist; +drop table if exists PlaylistTrack; +drop table if exists Track; +drop sequence if exists invoice_id_seq; + +create table Album +( + AlbumId integer not null, + Title nvarchar(160) not null, + ArtistId integer not null, + constraint pk_album primary key (AlbumId) +); + +create table Artist +( + ArtistId integer not null, + Name nvarchar(120), + constraint pk_artist primary key (ArtistId) +); + +create table Customer +( + CustomerId integer not null, + FirstName nvarchar(40) not null, + LastName nvarchar(20) not null, + Company nvarchar(80), + Address nvarchar(70), + City nvarchar(40), + State nvarchar(40), + Country nvarchar(40), + PostalCode nvarchar(10), + Phone nvarchar(24), + Fax nvarchar(24), + Email nvarchar(60) not null, + SupportRepId integer, + constraint pk_customer primary key (CustomerId) +); + +create table Employee +( + EmployeeId integer not null, + LastName nvarchar(20) not null, + FirstName nvarchar(20) not null, + Title nvarchar(30), + ReportsTo integer, + BirthDate date, + HireDate date, + Address nvarchar(70), + City nvarchar(40), + State nvarchar(40), + Country nvarchar(40), + PostalCode nvarchar(10), + Phone nvarchar(24), + Fax nvarchar(24), + Email nvarchar(60), + constraint pk_employee primary key (EmployeeId) +); + +create table Genre +( + GenreId integer not null, + Name nvarchar(120), + constraint pk_genre primary key (GenreId) +); + +create sequence invoice_id_seq start 1; + +create table Invoice +( + InvoiceId integer primary key default nextval('invoice_id_seq'), + CustomerId integer not null, + InvoiceDate date not null, + BillingAddress nvarchar(70), + BillingCity nvarchar(40), + BillingState nvarchar(40), + BillingCountry nvarchar(40), + BillingPostalCode nvarchar(10), + Total numeric(10,2) not null +); + +create table InvoiceLine +( + InvoiceLineId integer not null, + InvoiceId integer not null, + TrackId integer not null, + UnitPrice numeric(10,2) not null, + Quantity integer not null, + constraint pk_invoice_line primary key (InvoiceLineId) +); + +create table MediaType +( + MediaTypeId integer not null, + Name nvarchar(120), + constraint pk_media_type primary key (MediaTypeId) +); + +create table Playlist +( + PlaylistId integer not null, + Name nvarchar(120), + constraint pk_playlist primary key (PlaylistId) +); + +create table PlaylistTrack +( + PlaylistId integer not null, + TrackId integer not null, + constraint pk_playlist_track primary key (PlaylistId, TrackId) +); + +create table Track +( + TrackId integer not null, + Name nvarchar(200) not null, + AlbumId integer, + MediaTypeId integer not null, + GenreId integer, + Composer nvarchar(220), + Milliseconds integer not null, + Bytes integer, + UnitPrice numeric(10,2) not null, + constraint pk_track primary key (TrackId) +); + +create index ifk_album_artist_id on Album (ArtistId); +create index ifk_customer_support_rep_id on Customer (SupportRepId); +create index ifk_employee_reports_to on Employee (ReportsTo); +create index ifk_invoice_customer_id on Invoice (CustomerId); +create index ifk_invoice_item_invoice_id on InvoiceLine (InvoiceId); +create index ifk_invoice_item_track_id on InvoiceLine (TrackId); +create index ifk_playlist_track_track_id on PlaylistTrack (TrackId); +create index ifk_track_album_id on Track (AlbumId); +create index ifk_track_genre_id on Track (GenreId); +create index ifk_track_media_type_id on Track (MediaTypeId); + + +-- Inserting just enough data for the documentation to build +INSERT INTO Customer (customerId, firstName, lastName, company, address, city, state, country, postalCode, phone, fax, email, supportRepId) VALUES + (14, N'Mark', N'Philips', N'Telus', N'8210 111 ST NW', N'Edmonton', N'AB', N'Canada', N'T6G 2C7', N'+1 (780) 434-4554', N'+1 (780) 434-5565', N'mphilips12@shaw.ca', 5) \ No newline at end of file diff --git a/beam-duckdb/src/Database/Beam/DuckDB.hs b/beam-duckdb/src/Database/Beam/DuckDB.hs index f9ebd4f7e..4089b51a0 100644 --- a/beam-duckdb/src/Database/Beam/DuckDB.hs +++ b/beam-duckdb/src/Database/Beam/DuckDB.hs @@ -20,7 +20,9 @@ module Database.Beam.DuckDB ( -- * Executing DuckDB queries runBeamDuckDB, + -- ** Executing DuckDB queries with debugging runBeamDuckDBDebug, + runBeamDuckDBDebugString, -- * Backend datatype DuckDB, @@ -109,6 +111,12 @@ runBeamDuckDBDebug :: (Text -> IO ()) -> Connection -> DuckDBM a -> IO a runBeamDuckDBDebug debug conn action = runReaderT (runDuckDBM action) (debug, conn) +-- | Like 'runBeamDuckDBDebug', but accepts a 'String' argument instead of 'Text'. +-- +-- This is provided for compatibility with other backends +runBeamDuckDBDebugString :: (String -> IO ()) -> Connection -> DuckDBM a -> IO a +runBeamDuckDBDebugString debug = runBeamDuckDBDebug (debug . Text.unpack) + newtype BeamDuckDBParams = BeamDuckDBParams [SomeField] instance ToRow BeamDuckDBParams where diff --git a/beam-duckdb/src/Database/Beam/DuckDB/Backend.hs b/beam-duckdb/src/Database/Beam/DuckDB/Backend.hs index bce9cf1e5..37b83c15d 100644 --- a/beam-duckdb/src/Database/Beam/DuckDB/Backend.hs +++ b/beam-duckdb/src/Database/Beam/DuckDB/Backend.hs @@ -3,6 +3,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} @@ -12,36 +13,37 @@ module Database.Beam.DuckDB.Backend (DuckDB) where import Data.ByteString (ByteString) import Data.Data (Proxy (Proxy)) -import Data.Functor (($>)) +import Data.Functor (($>), (<&>)) import Data.Int (Int16, Int32, Int64, Int8) +import Data.Scientific (Scientific, scientific) import Data.Text (Text) import Data.Time (Day, LocalTime, TimeOfDay, UTCTime) import Data.UUID.Types (UUID) import Data.Word (Word16, Word32, Word64, Word8) import Database.Beam (HasQBuilder, HasSqlEqualityCheck, HasSqlInTable (..), HasSqlQuantifiedEqualityCheck) -import Database.Beam.Backend ( - BeamBackend (..), +import Database.Beam.Backend + ( BeamBackend (..), BeamSqlBackend, BeamSqlBackendIsString, BeamSqlBackendSyntax, FromBackendRow, SqlNull (..), parseOneField, - ) + ) import Database.Beam.Backend.SQL (FromBackendRow (..)) -import Database.Beam.DuckDB.Syntax ( - DuckDBCommandSyntax, +import Database.Beam.DuckDB.Syntax + ( DuckDBCommandSyntax, DuckDBExpressionSyntax (..), - ) -import Database.Beam.DuckDB.Syntax.Builder ( - commas, + ) +import Database.Beam.DuckDB.Syntax.Builder + ( commas, emit, parens, - ) + ) import Database.Beam.Query.SQL92 (buildSql92Query') import Database.Beam.Query.Types (HasQBuilder (..)) import Database.DuckDB.Simple (Null) -import Database.DuckDB.Simple.FromField (FromField) +import Database.DuckDB.Simple.FromField (DecimalValue (..), FromField) data DuckDB @@ -50,26 +52,26 @@ type instance BeamSqlBackendSyntax DuckDB = DuckDBCommandSyntax instance BeamSqlBackend DuckDB instance HasQBuilder DuckDB where - buildSqlQuery = buildSql92Query' True + buildSqlQuery = buildSql92Query' True instance HasSqlInTable DuckDB where - inRowValuesE Proxy e es = - DuckDBExpressionSyntax $ - mconcat - [ parens $ fromDuckDBExpression e - , emit " IN " - , parens $ emit "VALUES " <> commas (map fromDuckDBExpression es) - ] + inRowValuesE Proxy e es = + DuckDBExpressionSyntax $ + mconcat + [ parens $ fromDuckDBExpression e, + emit " IN ", + parens $ emit "VALUES " <> commas (map fromDuckDBExpression es) + ] instance BeamSqlBackendIsString DuckDB Text instance BeamSqlBackendIsString DuckDB String instance BeamBackend DuckDB where - type BackendFromField DuckDB = FromField + type BackendFromField DuckDB = FromField instance FromBackendRow DuckDB SqlNull where - fromBackendRow = parseOneField @DuckDB @Null $> SqlNull + fromBackendRow = parseOneField @DuckDB @Null $> SqlNull instance FromBackendRow DuckDB Bool @@ -113,6 +115,17 @@ instance FromBackendRow DuckDB LocalTime instance FromBackendRow DuckDB UTCTime +instance FromBackendRow DuckDB Scientific where + fromBackendRow = + parseOneField @DuckDB @DecimalValue + <&> decimalToScientific + +decimalToScientific :: DecimalValue -> Scientific +decimalToScientific decimalValue = + scientific + decimalValue.decimalInteger + (negate (fromIntegral decimalValue.decimalScale)) + instance HasSqlEqualityCheck DuckDB Bool instance HasSqlEqualityCheck DuckDB Float @@ -155,6 +168,8 @@ instance HasSqlEqualityCheck DuckDB LocalTime instance HasSqlEqualityCheck DuckDB UTCTime +instance HasSqlEqualityCheck DuckDB Scientific + instance HasSqlQuantifiedEqualityCheck DuckDB Bool instance HasSqlQuantifiedEqualityCheck DuckDB Float @@ -196,3 +211,5 @@ instance HasSqlQuantifiedEqualityCheck DuckDB TimeOfDay instance HasSqlQuantifiedEqualityCheck DuckDB LocalTime instance HasSqlQuantifiedEqualityCheck DuckDB UTCTime + +instance HasSqlQuantifiedEqualityCheck DuckDB Scientific diff --git a/beam-duckdb/src/Database/Beam/DuckDB/Syntax.hs b/beam-duckdb/src/Database/Beam/DuckDB/Syntax.hs index 36ab22bd4..e01d1ad6b 100644 --- a/beam-duckdb/src/Database/Beam/DuckDB/Syntax.hs +++ b/beam-duckdb/src/Database/Beam/DuckDB/Syntax.hs @@ -399,14 +399,12 @@ instance IsSql92ExpressionSyntax DuckDBExpressionSyntax where isNotNullE = postFix "IS NOT NULL" isNullE = postFix "IS NULL" - - -- SQLite doesn't handle tri-state booleans properly - isTrueE = postFix "IS 1" - isNotTrueE = postFix "IS NOT 1" - isFalseE = postFix "IS 0" - isNotFalseE = postFix "IS NOT 0" - isUnknownE = postFix "IS NULL" - isNotUnknownE = postFix "IS NOT NULL" + isTrueE = postFix "IS TRUE" + isFalseE = postFix "IS FALSE" + isNotTrueE = postFix "IS NOT TRUE" + isNotFalseE = postFix "IS NOT FALSE" + isUnknownE = postFix "IS UNKNOWN" + isNotUnknownE = postFix "IS NOT UNKNOWN" existsE select = DuckDBExpressionSyntax (emit "EXISTS " <> parens (fromDuckDBSelect select)) uniqueE select = DuckDBExpressionSyntax (emit "UNIQUE " <> parens (fromDuckDBSelect select)) diff --git a/build-docs.sh b/build-docs.sh index 3ea6f07c2..7c637a8b3 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -3,7 +3,7 @@ set -e get_pythonpath () { - export PYTHONPATH="`python -c "import sys; print(':'.join([s for s in sys.path if len(s) > 0]))"`:." + export PYTHONPATH="$(python -c "import sys; print(':'.join([s for s in sys.path if len(s) > 0]))"):." } servedocs () { diff --git a/docs/beam-templates/duckdb-parquet-out.hs b/docs/beam-templates/duckdb-parquet-out.hs new file mode 100644 index 000000000..e0ee6d059 --- /dev/null +++ b/docs/beam-templates/duckdb-parquet-out.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RecursiveDo #-} + +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XDeriveAnyClass -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_DIR: beam-sqlite/examples/ +-- ! FORMAT: console +module Main where + +import Database.Beam +import Database.Beam.DuckDB hiding (runBeamDuckDBDebug) +import qualified Database.Beam.DuckDB as DuckDB +import Database.DuckDB.Simple (withConnection) +import Database.Beam.Backend.Types + +import Control.Monad +import Control.Exception + +import qualified Data.List.NonEmpty as NonEmpty +import Data.Int +import Data.Text +import Data.Time (Day) + +import System.Environment (getEnv) + +data BeamDone = BeamDone + deriving (Show) +instance Exception BeamDone + +data ExamT f = Exam + { _examId :: Columnar f Int32, + _examName :: Columnar f Text, + _examScore :: Columnar f Double, + _examDate :: Columnar f Day + } + deriving (Generic) + +type Exam = ExamT Identity +deriving instance Show Exam +deriving instance Eq Exam + +instance Beamable ExamT +instance Table ExamT where + data PrimaryKey ExamT f = ExamId (Columnar f Int32) deriving (Generic) + primaryKey = ExamId . _examId +instance Beamable (PrimaryKey ExamT) + +data SchoolDB f = SchoolDB + { _exams :: f (DataSourceEntity ExamT) + } + deriving (Generic, Database DuckDB) + +main :: IO () +main = do + beamSource <- getEnv "BEAM_SOURCE" + let parquetPath = beamSource <> "/docs/.beam-query-cache/data/exams.parquet" + + schoolDB :: DatabaseSettings DuckDB SchoolDB + schoolDB = + defaultDbSettings + `withDbModification` (dbModification @_ @DuckDB) + { _exams = + dataSource (parquet (NonEmpty.singleton parquetPath)) + <> modifyDataSourceFields + tableModification + { _examId = "id", + _examName = "name", + _examScore = "score", + _examDate = "exam_date" + } + } + + withConnection ":memory:" $ \conn -> do + let runBeamDuckDBDebug _ = DuckDB.runBeamDuckDB + + BEAM_PLACEHOLDER diff --git a/docs/beam-templates/duckdb-parquet-sql.hs b/docs/beam-templates/duckdb-parquet-sql.hs new file mode 100644 index 000000000..dddef20fb --- /dev/null +++ b/docs/beam-templates/duckdb-parquet-sql.hs @@ -0,0 +1,81 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RecursiveDo #-} + +-- ! BUILD_COMMAND: runhaskell -XStandaloneDeriving -XTypeSynonymInstances -XDeriveGeneric -XOverloadedStrings -XFlexibleContexts -XFlexibleInstances -XTypeFamilies -XTypeApplications -XAllowAmbiguousTypes -XDeriveAnyClass -XPartialTypeSignatures -fno-warn-partial-type-signatures +-- ! BUILD_DIR: beam-sqlite/examples/ +-- ! FORMAT: sql +module Main where + +import Database.Beam +import Database.Beam.DuckDB hiding (runBeamDuckDBDebug) +import qualified Database.Beam.DuckDB as DuckDB +import Database.DuckDB.Simple (withConnection) +import Database.Beam.Backend.Types + +import Control.Monad +import Control.Exception + +import qualified Data.List.NonEmpty as NonEmpty +import Data.Int +import Data.Text +import Data.Time (Day) + +import System.Environment (getEnv) + +data BeamDone = BeamDone + deriving (Show) +instance Exception BeamDone + +data ExamT f = Exam + { _examId :: Columnar f Int32, + _examName :: Columnar f Text, + _examScore :: Columnar f Double, + _examDate :: Columnar f Day + } + deriving (Generic) + +type Exam = ExamT Identity +deriving instance Show Exam +deriving instance Eq Exam + +instance Beamable ExamT +instance Table ExamT where + data PrimaryKey ExamT f = ExamId (Columnar f Int32) deriving (Generic) + primaryKey = ExamId . _examId +instance Beamable (PrimaryKey ExamT) + +data SchoolDB f = SchoolDB + { _exams :: f (DataSourceEntity ExamT) + } + deriving (Generic, Database DuckDB) + +main :: IO () +main = do + beamSource <- getEnv "BEAM_SOURCE" + let parquetPath = beamSource <> "/docs/.beam-query-cache/data/exams.parquet" + + schoolDB :: DatabaseSettings DuckDB SchoolDB + schoolDB = + defaultDbSettings + `withDbModification` (dbModification @_ @DuckDB) + { _exams = + dataSource (parquet (NonEmpty.singleton parquetPath)) + <> modifyDataSourceFields + tableModification + { _examId = "id", + _examName = "name", + _examScore = "score", + _examDate = "exam_date" + } + } + + withConnection ":memory:" $ \conn -> do + let runBeamDuckDBDebug _ = DuckDB.runBeamDuckDBDebugString putStrLn + + ( do + -- Don't print the result + let print :: Show a => a -> IO () + print _ = pure () + + BEAM_PLACEHOLDER + ) \ No newline at end of file diff --git a/docs/beam.yaml b/docs/beam.yaml index 8aa5a2448..bc7642d6c 100644 --- a/docs/beam.yaml +++ b/docs/beam.yaml @@ -63,6 +63,36 @@ backends: - intersect - except + beam-duckdb: + backend-name: DuckDB + backend-module: Database.Beam.DuckDB + backend-options: chinook.ddb + src: + file: ./beam-duckdb + haskell-names: + package: beam-duckdb + backend: DuckDB + monad: DuckDBM + select-syntax: DuckDBSelectSyntax + mnemonic: DuckDB + # We need the version which accepts a 'String' rather than + # 'Text', for compatibility with other backends + with-database-debug: runBeamDuckDBDebugString + extra-packages: + - duckdb-simple + extra-imports: + - Database.DuckDB.Simple + backend-extra: >- + docsWithTransaction = withTransaction + supports: + - utf8 + # - t611 + - outer-join + # - window + - intersect + - except + - cte + # beam-mysql: # backend-name: MySQL # backend-module: Database.Beam.MySQL diff --git a/docs/data/exams.parquet b/docs/data/exams.parquet new file mode 100644 index 0000000000000000000000000000000000000000..2ada8687d5d2e1444f9a5b7c96c98039843cad66 GIT binary patch literal 1500 zcmb7EPfrt35PxsGEo;+gA->I9vxjQvMq@$#h%u&^zNJtIh)_ieA<<>KDM(9eTPWeE zd;{+uJ$f|8gW=@aqaVPdA$|hl%-gP%7EOHV?37lq6?rP|x48G0o} zj-O@5<$99o2Im@7B?RKR7R10j7kGQ?5_XvlASJ}7gi=nC47tnjKJL@d2OMx_E;a)?oJ*AV6sWaM!`9a@Fnf0CJ+Hvu`_sQgbj{NApU|PnV|h6L1thzM{t7;F66!9h2rQ-#p~!ty z=BClMTlkbJDnjgl*kvy_(=3eTw~9H$o7%(H@KntH$e3X6y9t5AFX zC1l>-Y3JMGK%%(#l>JB(=M%ymTikamD>yIAvxd9BO4*el-_A={cfPE@S($0B`^ $out - ''; - chinookSqliteRaw = fetchurl { - url = "https://raw.githubusercontent.com/lerocha/chinook-database/e7e6d5f008e35d3f89d8b8a4f8d38e3bfa7e34bd/ChinookDatabase/DataSources/Chinook_Sqlite.sql"; - sha256 = "sha256-Zu+IP8fhmYwpgofjtMJLvL8jFRlKJ43mjLANivq6Q9s="; - }; - chinookSqlite = runCommand "chinook-sqlite" {} '' - tail -c +4 ${chinookSqliteRaw} > $out - ''; - -in - -stdenv.mkDerivation { - pname = "beam-docs"; - version = "0"; - src = nix-gitignore.gitignoreFilterSource - (path: type: let - prefix = "${builtins.toPath ./..}/"; - strippedPath = lib.removePrefix prefix path; - in lib.or - (builtins.elem strippedPath [ - "beam-postgres" - "beam-sqlite" - ]) - (builtins.any (lib.flip lib.hasPrefix strippedPath) [ - "build-docs.sh" - "mkdocs.yml" - "docs" - "beam-postgres/examples" - "beam-postgres/beam-docs.sh" - "beam-sqlite/examples" - "beam-sqlite/beam-docs.sh" - ]) - ) - [] - ./..; - nativeBuildInputs = [ - # (beamGhc.ghc.withPackages beamLib.beamPackageList) - docsEnv - poetry - postgresql - sqlite - curl - pv - ]; - buildPhase = '' - mkdir postgres tmp - initdb -D postgres - echo "unix_socket_directories = '$(pwd)/tmp'" >> postgres/postgresql.conf - pg_ctl -D postgres start - - mkdir -p docs/.beam-query-cache/chinook-data - cp ${chinookPostgres} docs/.beam-query-cache/chinook-data/Chinook_PostgreSql.sql - cp ${chinookSqlite} docs/.beam-query-cache/chinook-data/Chinook_Sqlite.sql - CI=true BEAM_DOC_BACKEND="beam-postgres beam-sqlite" bash ./build-docs.sh builddocs - ''; - installPhase = '' - cp -r site $out - ''; -} diff --git a/docs/index.md b/docs/index.md index 6b5d36f1e..fb186617d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,6 +67,10 @@ Available backends are: to [the beam-sqlite documentation](user-guide/backends/beam-sqlite.md) for more information on compatibility. +* `beam-duckdb` -- A new backend for DuckDB. + See [the beam-duckdb documentation](user-guide/backends/beam-duckdb.md) + for more information. + * `beam-mysql` -- A backend for MySQL or MariaDB. Maintained separately on [GitHub](https://github.com/tathougies/beam-mysql). diff --git a/docs/markdown/beam_query.py b/docs/markdown/beam_query.py index bb6c3d2f6..9b7583e68 100644 --- a/docs/markdown/beam_query.py +++ b/docs/markdown/beam_query.py @@ -14,7 +14,7 @@ def check_ci(): - return os.environ.get("CI", "false") == "true" and "BEAM_DOC_BACKEND" in os.environ + return os.environ.get("CI", "false") == "true" def fetch_backend_src(backend_name, cache_dir, base_dir, src): @@ -313,7 +313,9 @@ def run_example(template_path, cache_dir, example_lines): if build_command.startswith("runhaskell"): ghc_args = build_command[len("runhaskell"):] if needs_th: - compile_command = f"ghc --make -O0 -o {binary_file} {ghc_args} {source_file} && {binary_file}" + # We use `-v0` so that the output of scripts is not "polluted" + # by the compiler output + compile_command = f"ghc -v0 --make -O0 -o {binary_file} {ghc_args} {source_file} && {binary_file}" else: compile_command = f"runhaskell {ghc_args} {source_file}" else: @@ -471,12 +473,7 @@ def extendMarkdown(self, md): conf = yaml.load(f, Loader=yaml.SafeLoader) backends = conf["backends"] - is_ci = check_ci() - enabled_backends = ( - self.getConfig("enabled_backends") - if not is_ci - else os.environ["BEAM_DOC_BACKEND"].split() - ) + enabled_backends = self.getConfig("enabled_backends") print("Enabled backends are", enabled_backends) if enabled_backends: backends = {k: v for k, v in backends.items() if k in enabled_backends} diff --git a/docs/user-guide/backends/beam-duckdb.md b/docs/user-guide/backends/beam-duckdb.md index 791d4e121..00ae96d14 100644 --- a/docs/user-guide/backends/beam-duckdb.md +++ b/docs/user-guide/backends/beam-duckdb.md @@ -41,23 +41,31 @@ data ExamT f = Exam _examDate :: Columnar f Day } --- Omitted various instances +type Exam = ExamT Identity +deriving instance Show Exam +deriving instance Eq Exam + +instance Beamable ExamT +instance Table ExamT where + data PrimaryKey ExamT f = ExamId (Columnar f Int32) deriving (Generic) + primaryKey = ExamId . _examId +instance Beamable (PrimaryKey ExamT) ``` Then, we can declare the database as having one "table" sources from a Parquet file: ```haskell -data ExampleDB f = ExampleDB - { _exams :: f (DataSourceEntity ExamT), +data SchoolDB f = SchoolDB + { _exams :: f (DataSourceEntity ExamT) } deriving (Generic, Database DuckDB) -exampleDb :: DatabaseSettings DuckDB ExampleDB -exampleDb = +schoolDB :: DatabaseSettings DuckDB SchoolDB +schoolDB = defaultDbSettings `withDbModification` (dbModification @_ @DuckDB) { _exams = - dataSource (parquet (NonEmpty.singleton "exams.parquet")) + dataSource (parquet (NonEmpty.singleton "data/exams.parquet")) <> modifyDataSourceFields tableModification { _examId = "id", @@ -74,12 +82,19 @@ multiple files with the same schema, or even one or more globs. Once this is done, you can query the "table" just like any other beam entity. For example, to fetch the maximum exam score: +!beam-query ```haskell -runSelectReturningOne - $ select - $ aggregate_ - (max_ . _examScore) - (allFromDataSource_ (_exams exampleDb)) +!duckdb-parquet-out output only:DuckDB +!duckdb-parquet-sql sql only:DuckDB +Just bestScore <- + runBeamDuckDBDebug putStrLn conn + $ runSelectReturningOne + $ select + $ aggregate_ + (max_ . _examScore) + (allFromDataSource_ (_exams schoolDB)) + +print bestScore ``` Note the one difference: instead of pulling all rows using `all_` (for a database table), @@ -93,8 +108,8 @@ Assume that we have a large Iceberg table, with the same `ExamT` schema as our P out the use of `parquet` in the example above with `icebergTable`: ```haskell -exampleDb :: DatabaseSettings DuckDB ExampleDB -exampleDb = +schoolDB :: DatabaseSettings DuckDB SchoolDB +schoolDB = defaultDbSettings `withDbModification` (dbModification @_ @DuckDB) { _exams = dataSource (icebergTable "s3://.../exams") @@ -135,8 +150,8 @@ We could instruct beam that we'll be querying from CSV files using `csv`. Howeve files have a header row, using `csvWith`: ```haskell -exampleDb :: DatabaseSettings DuckDB ExampleDB -exampleDb = +schoolDB :: DatabaseSettings DuckDB SchoolDB +schoolDB = defaultDbSettings `withDbModification` (dbModification @_ @DuckDB) { _exams = diff --git a/docs/user-guide/expressions.md b/docs/user-guide/expressions.md index 35e0105ba..94d47487d 100644 --- a/docs/user-guide/expressions.md +++ b/docs/user-guide/expressions.md @@ -183,7 +183,7 @@ in either Los Angeles or Manila: !beam-query ```haskell -!example chinook !on:Sqlite !on:MySQL +!example chinook !on:Sqlite !on:DuckDB filter_ (\c -> unknownAs_ False (addressCity (customerAddress c) ==*. anyIn_ [ just_ "Los Angeles", just_ "Manila" ])) $ all_ (customer chinookDb) ``` @@ -233,9 +233,10 @@ When a query is used in place of an expression it's called a *subquery*. A query For example, suppose we wish to offer a discount on all "short" tracks, where a track is considered short if its duration is less than the average track duration for all tracks. This is achieved using an update in which the predicate contains a subquery that calculates the average track duration. + !beam-query ```haskell -!example chinookdml +!example chinookdml !on:DuckDB runUpdate $ update (track chinookDb) (\track' -> trackUnitPrice track' <-. current_ (trackUnitPrice track') / 2) (\track' -> diff --git a/docs/user-guide/manipulation/delete.md b/docs/user-guide/manipulation/delete.md index 9d64b9599..c912cf176 100644 --- a/docs/user-guide/manipulation/delete.md +++ b/docs/user-guide/manipulation/delete.md @@ -22,7 +22,7 @@ For example, to delete any invoice with more than five invoice lines !beam-query ```haskell -!example chinookdml !on:Postgres !on:MySQL +!example chinookdml !on:Postgres runDelete $ delete (invoice chinookDb) (\i -> 5 <. subquery_ (aggregate_ (\_ -> as_ @Int32 countAll_) $ invoiceLines i)) diff --git a/docs/user-guide/manipulation/insert.md b/docs/user-guide/manipulation/insert.md index 997ac8486..9eeea67cb 100644 --- a/docs/user-guide/manipulation/insert.md +++ b/docs/user-guide/manipulation/insert.md @@ -51,9 +51,10 @@ value, but this may cause synchronization issues for our application. To do this, beam allows us to specify arbitrary expressions as a source of values using the `insertExpressions` function. + !beam-query ```haskell -!example chinookdml +!example chinookdml !on:DuckDB runInsert $ insert (invoice chinookDb) $ insertExpressions [ Invoice (val_ 800) (CustomerId (val_ 1)) currentTimestamp_ (val_ (Address (Just "123 My Street") (Just "Buenos Noches") (Just "Rio") (Just "Mozambique") (Just "ABCDEF"))) @@ -99,9 +100,10 @@ you want to use the default value for. For example, the query below adds a new invoice asking the database to assign a new id. + !beam-query ```haskell -!example chinookdml !on:Sqlite +!example chinookdml !on:Sqlite !on:DuckDB runInsert $ insert (invoice chinookDb) $ insertExpressions [ Invoice default_ -- Ask the database to give us a default id (val_ (CustomerId 1)) currentTimestamp_ @@ -126,9 +128,10 @@ you'll need to explicitly import `Database.Beam.Backend.SQL.BeamExtensions`. Below, we've imported this module qualified. + !beam-query ```haskell -!example chinookdml !on:MySQL +!example chinookdml !on:DuckDB [newInvoice] <- BeamExtensions.runInsertReturningList $ insert (invoice chinookDb) $ insertExpressions [ Invoice default_ -- Ask the database to give us a default id @@ -165,9 +168,10 @@ as your data. If not, you'll get a compile time error. For example, to create the playlists as above + !beam-query ```haskell -!example chinookdml +!example chinookdml !on:DuckDB runInsert $ insert (playlist chinookDb) $ insertFrom $ do @@ -195,9 +199,10 @@ columns. For example, suppose we want to insert new invoices for every customer with today's date. We can use the `insertOnly` function to project which field's are being inserted. + !beam-query ```haskell -!example chinookdml +!example chinookdml !on:DuckDB runInsert $ insertOnly (invoice chinookDb) @@ -290,9 +295,10 @@ runInsert $ Sometimes you only want to perform an action if a certain constraint is violated. If the conflicting index or constraint is on a field you can specify which fields with the function `conflictingFields`. + !beam-query ```haskell -!example chinookdml !on:MySQL +!example chinookdml !on:DuckDB --! import Database.Beam.Backend.SQL.BeamExtensions (BeamHasInsertOnConflict(..)) let newCustomer = Customer 42 "John" "Doe" Nothing (Address (Just "Street") (Just "City") (Just "State") Nothing Nothing) Nothing Nothing "john.doe@johndoe.com" nothing_ @@ -310,9 +316,10 @@ You can also specify how to change the record should it not match. For example, as an alternate when you insert an existing row, you can use the `oldValues` argument to get access to the old value. + !beam-query ```haskell -!example chinookdml !on:MySQL +!example chinookdml !on:DuckDB --! import Database.Beam.Backend.SQL.BeamExtensions (BeamHasInsertOnConflict(..)) let newCustomer = Customer 42 "John" "Doe" Nothing (Address (Just "Street") (Just "City") (Just "State") Nothing Nothing) Nothing Nothing "john.doe@johndoe.com" nothing_ @@ -326,9 +333,10 @@ runInsert $ If you want to be even more particular and only do this transformation on rows corresponding to customers from one state, use `conflictingFieldsWhere`. + !beam-query ```haskell -!example chinookdml !on:MySQL +!example chinookdml !on:DuckDB --! import Database.Beam.Backend.SQL.BeamExtensions (BeamHasInsertOnConflict(..)) let newCustomer = Customer 42 "John" "Doe" Nothing (Address (Just "Street") (Just "City") (Just "State") Nothing Nothing) Nothing Nothing "john.doe@johndoe.com" nothing_ diff --git a/docs/user-guide/manipulation/update.md b/docs/user-guide/manipulation/update.md index 1cb766ed0..c3a6dffa5 100644 --- a/docs/user-guide/manipulation/update.md +++ b/docs/user-guide/manipulation/update.md @@ -59,18 +59,21 @@ a boolean expression, and returns a `SqlUpdate`. For example, suppose Canada and the USA became one country and we needed to update all customer addresses to reflect that. + !beam-query ```haskell -!example chinookdml +!example chinookdml !on:DuckDB Just canadianCount <- runSelectReturningOne $ select $ - aggregate_ (\_ -> as_ @Int32 countAll_) $ + aggregate_ (\_ -> as_ @Int64 countAll_) $ filter_ (\c -> addressCountry (customerAddress c) ==. val_ (Just "Canada")) $ all_ (customer chinookDb) Just usaCount <- runSelectReturningOne $ select $ - aggregate_ (\_ -> as_ @Int32 countAll_) $ + aggregate_ (\_ -> as_ @Int64 countAll_) $ filter_ (\c -> addressCountry (customerAddress c) ==. val_ (Just "USA")) $ all_ (customer chinookDb) putStrLn ("Before, there were " ++ show canadianCount ++ " addresses in Canada and " ++ show usaCount ++ " in the USA.") @@ -82,12 +85,12 @@ runUpdate $ update (customer chinookDb) Just canadianCount' <- runSelectReturningOne $ select $ - aggregate_ (\_ -> as_ @Int32 countAll_) $ + aggregate_ (\_ -> as_ @Int64 countAll_) $ filter_ (\c -> addressCountry (customerAddress c) ==. val_ (Just "Canada")) $ all_ (customer chinookDb) Just usaCount' <- runSelectReturningOne $ select $ - aggregate_ (\_ -> as_ @Int32 countAll_) $ + aggregate_ (\_ -> as_ @Int64 countAll_) $ filter_ (\c -> addressCountry (customerAddress c) ==. val_ (Just "USA")) $ all_ (customer chinookDb) putStrLn ("Now, there are " ++ show canadianCount' ++ " addresses in Canada and " ++ show usaCount' ++ " in the USA.") diff --git a/docs/user-guide/queries/select.md b/docs/user-guide/queries/select.md index 94975d41f..2f4dd30d7 100644 --- a/docs/user-guide/queries/select.md +++ b/docs/user-guide/queries/select.md @@ -220,7 +220,7 @@ For example, to get all customers we know to be in New York, California, and Tex !beam-query ```haskell -!example chinook !on:Sqlite !on:MySQL +!example chinook !on:Sqlite do c <- all_ (customer chinookDb) st <- values_ [ "NY", "CA", "TX" ] guard_' (just_ st ==?. addressState (customerAddress c)) diff --git a/flake.nix b/flake.nix index 67e7b92b0..03e39898c 100644 --- a/flake.nix +++ b/flake.nix @@ -4,35 +4,51 @@ flake-parts.url = "github:hercules-ci/flake-parts"; haskell-flake.url = "github:srid/haskell-flake"; - testcontainers.url = - "github:testcontainers/testcontainers-hs/e286bd2ba9747c2d8c3756a3a89910e579e318e3"; + testcontainers.url = "github:testcontainers/testcontainers-hs/e286bd2ba9747c2d8c3756a3a89910e579e318e3"; testcontainers.flake = false; duckdb-ffi.url = "https://hackage.haskell.org/package/duckdb-ffi-1.4.1.5/duckdb-ffi-1.4.1.5.tar.gz"; duckdb-ffi.flake = false; }; - outputs = inputs@{ self, nixpkgs, flake-parts, ... }: + outputs = + inputs@{ + self, + nixpkgs, + flake-parts, + ... + }: flake-parts.lib.mkFlake { inherit inputs; } { systems = nixpkgs.lib.systems.flakeExposed; imports = [ inputs.haskell-flake.flakeModule ]; - perSystem = { self', pkgs, config, ... }: + perSystem = + { + self', + pkgs, + config, + ... + }: let chinookPostgresRaw = pkgs.fetchurl { url = "https://raw.githubusercontent.com/lerocha/chinook-database/e7e6d5f008e35d3f89d8b8a4f8d38e3bfa7e34bd/ChinookDatabase/DataSources/Chinook_PostgreSql.sql"; sha256 = "sha256-CVQAyq0WlAn7+0d72nsm9krVDLtMA1QcgHJhwdttNC4="; }; - chinookPostgres = pkgs.runCommand "chinook-postgres" {} '' - ${pkgs.glibc.bin}/bin/iconv -f ISO-8859-2 -t UTF-8 ${chinookPostgresRaw} > $out - ''; + chinookPostgres = + pkgs.runCommand "chinook-postgres" + { + nativeBuildInputs = if pkgs.stdenv.isDarwin then [ pkgs.libiconv ] else [ pkgs.glibc.bin ]; + } + '' + iconv -f ISO-8859-2 -t UTF-8 ${chinookPostgresRaw} > $out + ''; chinookSqliteRaw = pkgs.fetchurl { url = "https://raw.githubusercontent.com/lerocha/chinook-database/e7e6d5f008e35d3f89d8b8a4f8d38e3bfa7e34bd/ChinookDatabase/DataSources/Chinook_Sqlite.sql"; sha256 = "sha256-Zu+IP8fhmYwpgofjtMJLvL8jFRlKJ43mjLANivq6Q9s="; }; - chinookSqlite = pkgs.runCommand "chinook-sqlite" {} '' + chinookSqlite = pkgs.runCommand "chinook-sqlite" { } '' tail -c +4 ${chinookSqliteRaw} > $out ''; @@ -49,10 +65,12 @@ hp.beam-migrate hp.beam-postgres hp.beam-sqlite + hp.beam-duckdb hp.microlens-th ]); - in { + in + { haskellProjects.default = { basePackages = pkgs.haskell.packages.ghc910; @@ -64,6 +82,9 @@ settings = { testcontainers.check = false; beam-postgres.check = false; + beam-duckdb.check = false; + duckdb-simple.check = false; + duckdb-simple.jailbreak = true; # Version bounds are incompatible with GHC 9.10 it seems duckdb-ffi.broken = false; duckdb-ffi.check = false; duckdb-ffi.extraLibraries = [ pkgs.duckdb ]; @@ -82,7 +103,7 @@ curl pv duckdb - ; + ; }; }; }; @@ -95,6 +116,7 @@ self'.packages.beam-postgres self'.packages.pagila self'.packages.beam-sqlite + self'.packages.beam-duckdb ]; }; @@ -103,27 +125,33 @@ packages.docs = pkgs.stdenv.mkDerivation { pname = "beam-docs"; version = "0"; - src = pkgs.nix-gitignore.gitignoreFilterSource - (path: type: let + src = pkgs.nix-gitignore.gitignoreFilterSource ( + path: type: + let prefix = "${builtins.toPath ./.}/"; strippedPath = pkgs.lib.removePrefix prefix path; - in pkgs.lib.or + in + pkgs.lib.or (builtins.elem strippedPath [ "beam-postgres" "beam-sqlite" + "beam-duckdb" ]) - (builtins.any (pkgs.lib.flip pkgs.lib.hasPrefix strippedPath) [ - "build-docs.sh" - "mkdocs.yml" - "docs" - "beam-postgres/examples" - "beam-postgres/beam-docs.sh" - "beam-sqlite/examples" - "beam-sqlite/beam-docs.sh" - ]) - ) - [] - ./.; + ( + builtins.any (pkgs.lib.flip pkgs.lib.hasPrefix strippedPath) [ + "build-docs.sh" + "mkdocs.yml" + "docs" + "docs/data" + "beam-postgres/examples" + "beam-postgres/beam-docs.sh" + "beam-sqlite/examples" + "beam-sqlite/beam-docs.sh" + "beam-duckdb/beam-docs.sh" + "beam-duckdb/docs" + ] + ) + ) [ ] ./.; nativeBuildInputs = [ ghcWithBeam @@ -132,9 +160,25 @@ pkgs.sqlite pkgs.curl pkgs.pv + pkgs.duckdb ]; buildPhase = '' + # Required for DuckDB + # We need the location of the source because DuckDB + # has trouble finding data files in Nix (e.g. Parquet files) + # unless the path is absolute + export HOME=$(mktemp -d) + export BEAM_SOURCE=$(pwd) + + mkdir -p docs/.beam-query-cache/data + cp docs/data/exams.parquet docs/.beam-query-cache/data/exams.parquet + + mkdir -p docs/.beam-query-cache/chinook-data + cp ${chinookPostgres} docs/.beam-query-cache/chinook-data/Chinook_PostgreSql.sql + cp ${chinookSqlite} docs/.beam-query-cache/chinook-data/Chinook_Sqlite.sql + cp beam-duckdb/docs/Chinook.sql docs/.beam-query-cache/chinook-data/Chinook_DuckDB.sql + mkdir postgres export PGHOST=$(mktemp -d /tmp/pg.XXXXXX) initdb -D postgres @@ -142,10 +186,7 @@ pg_ctl -D postgres -o "-c listen_addresses=localhost" start trap "pg_ctl -D postgres stop -m immediate" EXIT - mkdir -p docs/.beam-query-cache/chinook-data - cp ${chinookPostgres} docs/.beam-query-cache/chinook-data/Chinook_PostgreSql.sql - cp ${chinookSqlite} docs/.beam-query-cache/chinook-data/Chinook_Sqlite.sql - CI=true BEAM_DOC_BACKEND="beam-postgres beam-sqlite" bash ./build-docs.sh builddocs + CI=true bash ./build-docs.sh builddocs ''; installPhase = '' diff --git a/mkdocs.yml b/mkdocs.yml index baf49c1d1..59d0d7a3d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,8 +53,7 @@ markdown_extensions: enabled_backends: - beam-postgres - beam-sqlite - # - beam-mysql - # - beam-mssql + - beam-duckdb - docs.markdown.markdown_fenced_code_tabs - def_list - codehilite: