-
Notifications
You must be signed in to change notification settings - Fork 296
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
Postgresql constraint=
attribute support
#979
Changes from all commits
f4802be
6b35638
142856d
d67dc37
8ce0e70
6493074
3d4504b
bd58dcf
7220023
72dd219
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -568,7 +568,7 @@ migrate' allDefs getter entity = fmap (fmap $ map showAlterDb) $ do | |
migrationText exists old'' = | ||
if not exists | ||
then createText newcols fdefs udspair | ||
else let (acs, ats) = getAlters allDefs entity (newcols, udspair) old' | ||
else let (acs, ats) = getAlters allDefs entity (newcols, udspair) $ excludeForeignKeys $ old' | ||
acs' = map (AlterColumn name) acs | ||
ats' = map (AlterTable name) ats | ||
in acs' ++ ats' | ||
|
@@ -577,6 +577,13 @@ migrate' allDefs getter entity = fmap (fmap $ map showAlterDb) $ do | |
(newcols', udefs, fdefs) = mkColumns allDefs entity | ||
newcols = filter (not . safeToRemove entity . cName) newcols' | ||
udspair = map udToPair udefs | ||
excludeForeignKeys (xs,ys) = (map excludeForeignKey xs,ys) | ||
excludeForeignKey c = case cReference c of | ||
Just (_,fk) -> | ||
case find (\f -> fk == foreignConstraintNameDBName f) fdefs of | ||
Just _ -> c { cReference = Nothing } | ||
Nothing -> c | ||
Nothing -> c | ||
-- Check for table existence if there are no columns, workaround | ||
-- for https://github.com/yesodweb/persistent/issues/152 | ||
|
||
|
@@ -792,23 +799,27 @@ getColumn getter tableName' [PersistText columnName, PersistText isNullable, Per | |
Nothing -> loop' ps | ||
Just t' -> t' | ||
getRef cname = do | ||
let sql = T.concat | ||
[ "SELECT COUNT(*) FROM " | ||
, "information_schema.table_constraints " | ||
, "WHERE table_catalog=current_database() " | ||
, "AND table_schema=current_schema() " | ||
, "AND table_name=? " | ||
, "AND constraint_type='FOREIGN KEY' " | ||
, "AND constraint_name=?" | ||
] | ||
let ref = refName tableName' cname | ||
let sql = T.concat ["SELECT DISTINCT " | ||
,"ccu.table_name, " | ||
,"tc.constraint_name " | ||
,"FROM information_schema.constraint_column_usage ccu, " | ||
,"information_schema.key_column_usage kcu, " | ||
,"information_schema.table_constraints tc " | ||
,"WHERE tc.constraint_type='FOREIGN KEY' " | ||
,"AND kcu.constraint_name=tc.constraint_name " | ||
,"AND ccu.constraint_name=kcu.constraint_name " | ||
,"AND kcu.ordinal_position=1 " | ||
,"AND kcu.table_name=? " | ||
,"AND kcu.column_name=?"] | ||
stmt <- getter sql | ||
with (stmtQuery stmt | ||
[ PersistText $ unDBName tableName' | ||
, PersistText $ unDBName ref | ||
]) (\src -> runConduit $ src .| do | ||
Just [PersistInt64 i] <- CL.head | ||
return $ if i == 0 then Nothing else Just (DBName "", ref)) | ||
cntrs <- with (stmtQuery stmt [PersistText $ unDBName tableName' | ||
,PersistText $ unDBName cname]) | ||
(\src -> runConduit $ src .| CL.consume) | ||
case cntrs of | ||
[] -> return Nothing | ||
[[PersistText table, PersistText constraint]] -> | ||
return $ Just (DBName table, DBName constraint) | ||
_ -> error "Postgresql.getColumn: error fetching constraints" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robbassi So this is the place where the error is thrown. Is there a reason you assume a single constraint per table here and throw the error? A simple idea of a schema that this breaks is a table that records sales like:
|
||
d' = case defaultValue of | ||
PersistNull -> Right Nothing | ||
PersistText t -> Right $ Just t | ||
|
@@ -908,7 +919,7 @@ getAddReference :: [EntityDef] -> DBName -> DBName -> DBName -> Maybe (DBName, D | |
getAddReference allDefs table reftable cname ref = | ||
case ref of | ||
Nothing -> Nothing | ||
Just (s, _) -> Just $ AlterColumn table (s, AddReference (refName table cname) [cname] id_) | ||
Just (s, constraintName) -> Just $ AlterColumn table (s, AddReference constraintName [cname] id_) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice |
||
where | ||
id_ = fromMaybe (error $ "Could not find ID of entity " ++ show reftable) | ||
$ do | ||
|
@@ -1140,10 +1151,6 @@ instance PersistConfig PostgresConf where | |
addPass = maybeAddParam "password" "PGPASS" | ||
addDatabase = maybeAddParam "dbname" "PGDATABASE" | ||
|
||
refName :: DBName -> DBName -> DBName | ||
refName (DBName table) (DBName column) = | ||
DBName $ T.concat [table, "_", column, "_fkey"] | ||
|
||
udToPair :: UniqueDef -> (DBName, [DBName]) | ||
udToPair ud = (uniqueDBName ud, map snd $ uniqueFields ud) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
{-# LANGUAGE EmptyDataDecls #-} | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE GADTs #-} | ||
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE QuasiQuotes #-} | ||
{-# LANGUAGE TemplateHaskell #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
{-# LANGUAGE UndecidableInstances #-} | ||
module CustomConstraintTest where | ||
|
||
import PgInit | ||
import qualified Data.Text as T | ||
|
||
share [mkPersist sqlSettings, mkMigrate "customConstraintMigrate"] [persistLowerCase| | ||
CustomConstraint1 | ||
some_field Text | ||
deriving Show | ||
|
||
CustomConstraint2 | ||
cc_id CustomConstraint1Id constraint=custom_constraint | ||
deriving Show | ||
|] | ||
|
||
specs :: (MonadIO m, MonadFail m) => RunDb SqlBackend m -> Spec | ||
specs runDb = do | ||
describe "custom constraint used in migration" $ do | ||
it "custom constraint is actually created" $ runDb $ do | ||
runMigration customConstraintMigrate | ||
runMigration customConstraintMigrate -- run a second time to ensure the constraint isn't dropped | ||
let query = T.concat ["SELECT DISTINCT COUNT(*) " | ||
,"FROM information_schema.constraint_column_usage ccu, " | ||
,"information_schema.key_column_usage kcu, " | ||
,"information_schema.table_constraints tc " | ||
,"WHERE tc.constraint_type='FOREIGN KEY' " | ||
,"AND kcu.constraint_name=tc.constraint_name " | ||
,"AND ccu.constraint_name=kcu.constraint_name " | ||
,"AND kcu.ordinal_position=1 " | ||
,"AND ccu.table_name=? " | ||
,"AND ccu.column_name=? " | ||
,"AND kcu.table_name=? " | ||
,"AND kcu.column_name=? " | ||
,"AND tc.constraint_name=?"] | ||
[Single exists] <- rawSql query [PersistText "custom_constraint1" | ||
,PersistText "id" | ||
,PersistText "custom_constraint2" | ||
,PersistText "cc_id" | ||
,PersistText "custom_constraint"] | ||
liftIO $ 1 @?= (exists :: Int) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the way postgresql stores the column metadata, I had to use
DISTINCT
here in order to avoid duplicate rows being returned for composite keys.