@@ -8,15 +8,13 @@ The schema cache is necessary for resource embedding, foreign keys are used for
88
99These queries are executed once at startup or when PostgREST is reloaded.
1010-}
11- {-# LANGUAGE DeriveAnyClass #-}
12- {-# LANGUAGE DeriveGeneric #-}
13- {-# LANGUAGE FlexibleContexts #-}
14- {-# LANGUAGE MultiParamTypeClasses #-}
15- {-# LANGUAGE NamedFieldPuns #-}
16- {-# LANGUAGE QuasiQuotes #-}
17- {-# LANGUAGE RecordWildCards #-}
18- {-# LANGUAGE ScopedTypeVariables #-}
19- {-# LANGUAGE TypeSynonymInstances #-}
11+ {-# LANGUAGE DeriveAnyClass #-}
12+ {-# LANGUAGE DeriveGeneric #-}
13+ {-# LANGUAGE FlexibleContexts #-}
14+ {-# LANGUAGE NamedFieldPuns #-}
15+ {-# LANGUAGE QuasiQuotes #-}
16+ {-# LANGUAGE RecordWildCards #-}
17+ {-# LANGUAGE ScopedTypeVariables #-}
2018
2119module PostgREST.SchemaCache
2220 ( SchemaCache (.. )
@@ -44,31 +42,36 @@ import qualified Hasql.Transaction as SQL
4442import Data.Functor.Contravariant ((>$<) )
4543import NeatInterpolation (trimming )
4644
47- import PostgREST.Config (AppConfig (.. ),
48- LogLevel (.. ))
49- import PostgREST.Config.Database (TimezoneNames ,
50- toIsolationLevel )
51- import PostgREST.SchemaCache.Identifiers (FieldName ,
52- QualifiedIdentifier (.. ),
53- RelIdentifier (.. ),
54- Schema , escapeIdent ,
55- isAnyElement )
56- import PostgREST.SchemaCache.Relationship (Cardinality (.. ),
57- Junction (.. ),
58- Relationship (.. ),
59- RelationshipsMap )
60- import PostgREST.SchemaCache.Representations (DataRepresentation (.. ),
61- RepresentationsMap )
62- import PostgREST.SchemaCache.Routine (FuncVolatility (.. ),
63- MediaHandler (.. ),
64- MediaHandlerMap ,
65- PgType (.. ),
66- RetType (.. ),
67- Routine (.. ),
68- RoutineMap ,
69- RoutineParam (.. ))
70- import PostgREST.SchemaCache.Table (Column (.. ), ColumnMap ,
71- Table (.. ), TablesMap )
45+ import PostgREST.Config (AppConfig (.. ),
46+ LogLevel (.. ))
47+ import PostgREST.Config.Database (TimezoneNames ,
48+ toIsolationLevel )
49+ import PostgREST.SchemaCache.Identifiers (FieldName ,
50+ QualifiedIdentifier (.. ),
51+ RelIdentifier (.. ),
52+ Schema ,
53+ escapeIdent ,
54+ isAnyElement )
55+ import PostgREST.SchemaCache.Relationship (Cardinality (.. ),
56+ Junction (.. ),
57+ Relationship (.. ),
58+ RelationshipsMap )
59+ import PostgREST.SchemaCache.Representations (DataRepresentation (.. ),
60+ RepresentationsMap )
61+ import PostgREST.SchemaCache.Routine (FuncVolatility (.. ),
62+ MediaHandler (.. ),
63+ MediaHandlerMap ,
64+ PgType (.. ),
65+ RetType (.. ),
66+ Routine (.. ),
67+ RoutineMap ,
68+ RoutineParam (.. ))
69+ import PostgREST.SchemaCache.Table (Column (.. ),
70+ ColumnMap ,
71+ Table (.. ),
72+ TablesMap )
73+ import PostgREST.SqlTransaction (SqlTransaction )
74+ import qualified PostgREST.SqlTransaction as SQLT
7275
7376import qualified PostgREST.MediaType as MediaType
7477
@@ -157,46 +160,55 @@ maxDbTablesForFuzzySearch = 500
157160querySchemaCache :: AppConfig -> SQL. Transaction SchemaCache
158161querySchemaCache conf@ AppConfig {.. } = do
159162 SQL. sql " set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
160- tabs <- sqlTimedStmt gucTbls conf allTables
161- keyDeps <- sqlTimedStmt gucKDeps conf allViewsKeyDependencies
162- m2oRels <- sqlTimedStmt gucRels mempty allM2OandO2ORels
163- funcs <- sqlTimedStmt gucFuncs conf allFunctions
164- cRels <- sqlTimedStmt gucCRels mempty allComputedRels
165- reps <- sqlTimedStmt gucDReps conf dataRepresentations
166- mHdlers <- sqlTimedStmt gucMHdrs conf mediaHandlers
167- tzones <- if configDbTimezoneEnabled
168- then sqlTimedStmt gucTzones mempty timezones
169- else pure S. empty
170- _ <-
171- let sleepCall = SQL. Statement " select pg_sleep($1 / 1000.0)" (param HE. int4) HD. noResult True in
172- for_ configInternalSCQuerySleep (`SQL.statement` sleepCall) -- only used for testing
173-
174- qsTime <-
175- if isLogDebug
176- then Just <$> SQL. statement mempty (extractTimings configDbTimezoneEnabled)
177- else pure Nothing
178-
179- let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
180- rels = addInverseRels $ addM2MRels tabsWViewsPks $ addViewM2OAndO2ORels keyDeps m2oRels
181-
182- return $ removeInternal schemas $ SchemaCache {
183- dbTables = tabsWViewsPks
184- , dbRelationships = getOverrideRelationshipsMap rels cRels
185- , dbRoutines = funcs
186- , dbRepresentations = reps
187- , dbMediaHandlers = HM. union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
188- , dbTimezones = tzones
189-
190- , dbTablesFuzzyIndex =
191- -- Only build fuzzy index for schemas with a reasonable number of tables
192- -- Fuzzy.FuzzySet is memory heavy we just don't use it for large schemas
193- Fuzzy. fromList <$> HM. filter ((< maxDbTablesForFuzzySearch) . length ) (HM. fromListWith (<>) ((qiSchema &&& pure . qiName) <$> HM. keys tabsWViewsPks))
194- , dbQueryTimings = qsTime
195- }
163+ -- if configInternalSCLockId is set run queries step-by-step waiting for lock release before each
164+ maybe
165+ querySchemaCacheSqlTransaction
166+ (SQLT. runSteppedTransaction querySchemaCacheSqlTransaction)
167+ configInternalSCLockId
168+ -- TODO remove configInternalSCQuerySleep once all tests are migrated to stepped execution
169+ <* for_ configInternalSCQuerySleep (`SQL.statement` sleepCall) -- only used for testing
196170 where
171+ querySchemaCacheSqlTransaction :: forall m . SqlTransaction m => m SchemaCache
172+ querySchemaCacheSqlTransaction = do
173+ tabs <- sqlTimedStmt gucTbls conf allTables
174+ keyDeps <- sqlTimedStmt gucKDeps conf allViewsKeyDependencies
175+ m2oRels <- sqlTimedStmt gucRels mempty allM2OandO2ORels
176+ funcs <- sqlTimedStmt gucFuncs conf allFunctions
177+ cRels <- sqlTimedStmt gucCRels mempty allComputedRels
178+ reps <- sqlTimedStmt gucDReps conf dataRepresentations
179+ mHdlers <- sqlTimedStmt gucMHdrs conf mediaHandlers
180+ tzones <- if configDbTimezoneEnabled
181+ then sqlTimedStmt gucTzones mempty timezones
182+ else pure S. empty
183+
184+ qsTime <-
185+ if isLogDebug
186+ then Just <$> SQLT. statement mempty (extractTimings configDbTimezoneEnabled)
187+ else pure Nothing
188+
189+ let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
190+ rels = addInverseRels $ addM2MRels tabsWViewsPks $ addViewM2OAndO2ORels keyDeps m2oRels
191+
192+ return $ removeInternal schemas $ SchemaCache {
193+ dbTables = tabsWViewsPks
194+ , dbRelationships = getOverrideRelationshipsMap rels cRels
195+ , dbRoutines = funcs
196+ , dbRepresentations = reps
197+ , dbMediaHandlers = HM. union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
198+ , dbTimezones = tzones
199+
200+ , dbTablesFuzzyIndex =
201+ -- Only build fuzzy index for schemas with a reasonable number of tables
202+ -- Fuzzy.FuzzySet is memory heavy we just don't use it for large schemas
203+ Fuzzy. fromList <$> HM. filter ((< maxDbTablesForFuzzySearch) . length ) (HM. fromListWith (<>) ((qiSchema &&& pure . qiName) <$> HM. keys tabsWViewsPks))
204+ , dbQueryTimings = qsTime
205+ }
206+
197207 schemas = toList configDbSchemas
198208 isLogDebug = configLogLevel == LogDebug
209+ sqlTimedStmt :: SqlTransaction m => ByteString -> a -> SQL. Statement a b -> m b
199210 sqlTimedStmt = sqlTimedStatement isLogDebug
211+ sleepCall = SQL. Statement " select pg_sleep($1 / 1000.0)" (param HE. int4) HD. noResult True
200212
201213-- | overrides detected relationships with the computed relationships and gets the RelationshipsMap
202214getOverrideRelationshipsMap :: [Relationship ] -> [Relationship ] -> RelationshipsMap
@@ -1167,22 +1179,22 @@ arrayColumn = column . HD.listArray . HD.nonNullable
11671179 -
11681180 - We can do this for several statements inside the transaction. The timings are later captured at the end of the transaction with extractTimings.
11691181 -}
1170- sqlTimedStatement :: Bool -> ByteString -> a -> SQL. Statement a b -> SQL. Transaction b
1182+ sqlTimedStatement :: SqlTransaction m => Bool -> ByteString -> a -> SQL. Statement a b -> m b
11711183sqlTimedStatement isLogDebug guc params stmt =
11721184 if isLogDebug then
1173- SQL . sql sFrag >> SQL . statement params stmt <* SQL . sql eFrag
1185+ SQLT . sql sFrag >> SQLT . statement params stmt <* SQLT . sql eFrag
11741186 else
1175- SQL . statement params stmt
1187+ SQLT . statement params stmt
11761188 where
11771189 sFrag = " select set_config('pgrst." <> guc <> " ', clock_timestamp()::text, true)"
11781190 eFrag = " select set_config('pgrst." <> guc <> " ', (clock_timestamp() - current_setting('pgrst." <> guc <> " ', false)::timestamptz)::text, true)"
11791191
11801192-- Extract all the generated timings (see sqlTimedStatement) converting the value to milliseconds.
11811193extractTimings :: Bool -> SQL. Statement () QueryTimings
1182- extractTimings hasTimezones = SQL. Statement sql HE. noParams decodeThem True
1194+ extractTimings hasTimezones = SQL. Statement query HE. noParams decodeThem True
11831195 where
11841196 qFrag setting = " extract('milliseconds' from current_setting('pgrst." <> setting <> " ', false)::interval)::text"
1185- sql = " SELECT " <> BS. intercalate " ,"
1197+ query = " SELECT " <> BS. intercalate " ,"
11861198 [ qFrag gucTbls, qFrag gucKDeps, qFrag gucRels
11871199 , qFrag gucFuncs, qFrag gucCRels, qFrag gucDReps
11881200 , qFrag gucMHdrs, if hasTimezones then qFrag gucTzones else " '0.0'"
0 commit comments