Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schema support to Persistent (issue #93) #1561

Open
wants to merge 74 commits into
base: master
Choose a base branch
from

Conversation

curranosaurus
Copy link

@curranosaurus curranosaurus commented Dec 30, 2024

Addresses #93 by adding schema support to Persistent. You specify a Persistent model's schema using syntax like schema=foo.
I worked on this together with @benjonesy.

Design challenges and decisions

"Schema" means many things to many backends

In this PR, we use the word "schema" to refer to various devices that backends offer for namespacing tables.

  • ✔️ In PostgreSQL, a schema exists in between the level of a table and a database.
  • ✔️ In MySQL and SQLite, schemas and databases are synonymous concepts.
  • ❌ MongoDB and Redis do not have a concept analogous to schemas. This PR does not extend support to these backends.

Schemas may or may not exist

We considered designing this PR so that Persistent would create schemas for the user if they did not already exist. But we chose not to do that. This means that local development with Persistent using schemas will require some manual SQL migrations to create the schemas that the user wants. But for conventional use-cases, schema creation will be a rare event, so this is not very onerous for developers. Below are some issues one would have to solve to make Persistent automate schema creation for the developer:

Three-stage migrations

RIght now, the CREATe TABLE DDL statements are sorted to the top of the migration output, so that the (potentially recursive) foreign-key constraints are created only after all of the tables they reference exist. If we also wanted Persistent to ensure the existence of schemas, we would need to sort backend-appropriate versions of CREATE SCHEMA statements above the CREATe TABLE statements.

Granting permissions in MySQL

Before a connection can use a newly created database in MySQL, one must run a GRANT statement to give the current user the requisite DDL operations to run CREATe TABLE statements. We would somehow need to make Persistent aware of the GRANT statement required.

File name and directory in SQLite

SQLite's invocation for creating a new database also specifies the file name and location for that database's .db file. Persistent would need to be opinionated about this directory scheme, or we would have to add a configuration option, if we wanted to support schema creation in SQLite.

PR Template checklist

Before submitting your PR, check that you've:

  • Documented new APIs with Haddock markup
  • Added @since declarations to the Haddock
  • Ran stylish-haskell on any changed files.
  • Adhered to the code style (see the .editorconfig file for details)

After submitting your PR:

  • Update the Changelog.md file with a link to your PR
  • Bumped the version number if there isn't an (unreleased) on the Changelog
  • Check that CI passes (or if it fails, for reasons unrelated to your change, like CI timeouts)

curranosaurus and others added 30 commits September 30, 2024 15:56
Add new `entitySchema` field and refactor callsites of `entityDB` to use `getEntityDBName` instead
[INT-844] prepend schema to entity db name
…ntityDBName

revert change to `getEntityDBName`
…tters-for-entitySchema

add setters and getters for `entitySchema`
…b.com:curranosaurus/persistent into curran/update-entitySchema-to-its-own-newtype
…o-its-own-newtype

Create newtype for schema name
…t-schema

update sqlQQ test to consider a case with a schema
…s-migrations

Use new entitySchema field for Postgres migrations
…sql-queries

Start using table schema in queries with Postgres
import qualified PersistentTest
import qualified PersistUniqueTest
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted imports due to stylish-haskell.

, (==@)
, (@/=)
, (@==)
)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reformatted by stylish-haskell.

import PersistentTestModels
import qualified CodeGenTest
import PersistTestPetType
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports sorted by stylish-haskell.

import qualified PersistentTest
import qualified PersistUniqueTest
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports sorted by stylish-haskell.

import qualified PersistentTest
import qualified PersistUniqueTest
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports sorted by stylish-haskell.

@@ -539,7 +541,7 @@ getCopyTable :: [EntityDef]
-> EntityDef
-> IO [(Bool, Text)]
getCopyTable allDefs getter def = do
Copy link
Author

@curranosaurus curranosaurus Dec 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to this function were non-trivial. A naive change to this function would allow identically named tables in different schemas to clobber each other when copying them into the temporary schema. So we do some manual namespacing by prefixing the table name with <schema>_ when copying it to the temporary schema.

@@ -470,6 +470,8 @@ migrate' allDefs getter val = do
where
def = val
table = getEntityDBName def
schema = getEntitySchema def
sqliteMaster = maybe "sqlite_master" (\schema' -> escapeS schema' <> ".sqlite_master") schema
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one copy of the sqlite_master table in each schema, and it only contains information about the tables in its own schema.

, fcOnDelete = parseCascadeAction onDel
}
then Just $
let colSchema =
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the most complex pieces of logic in the persistent-mysql diff, it's worth scrutinizing whether this makes sense.

@curranosaurus
Copy link
Author

I've run the test suites for the changed packages, and they all pass except for persistent-mysql. But the test failure I get there also occurs on master branch for me, so seems unrelated to these changes. Here is the failure output I get, for reference:

Failures:

  src/ForeignKey.hs:198:12: 
  1) foreign keys options deletes cascades with field self reference to the whole chain
       expected: []
        but got: [Entity {entityKey = Chain3Key {unChain3Key = SqlBackendKey {unSqlBackendKey = 92}}, entityVal = Chain3 {chain
3Name = 2, chain3Previous = Just (Chain3Key {unChain3Key = SqlBackendKey {unSqlBackendKey = 91}})}},Entity {entityKey = Chain3K
ey {unChain3Key = SqlBackendKey {unSqlBackendKey = 93}}, entityVal = Chain3 {chain3Name = 3, chain3Previous = Just (Chain3Key {
unChain3Key = SqlBackendKey {unSqlBackendKey = 92}})}}]

  To rerun use: --match "/foreign keys options/deletes cascades with field self reference to the whole chain/" --seed 6043809

Randomized with seed 6043809

@curranosaurus curranosaurus marked this pull request as ready for review December 31, 2024 00:48
@@ -37,6 +40,7 @@ runConn f = do
db :: SqlPersistT (LoggingT (ResourceT IO)) a -> IO a
db actions = do
runResourceT $ runConn $ do
rawSql @(Single Int64) ("attach 'animals.db' as animals") []
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that the tests referencing the animals schema succeed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants