mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
[server] metadata API for native access
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7476 Co-authored-by: Tom Harding <6302310+i-am-tom@users.noreply.github.com> GitOrigin-RevId: 781c29666e92004dc82918c2292fdacc27fded4c
This commit is contained in:
parent
4175b53395
commit
06b284cf33
@ -25012,6 +25012,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"custom_sql": null,
|
||||
"functions": [
|
||||
{
|
||||
"function": {
|
||||
@ -26645,4 +26646,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12990,6 +12990,7 @@ sources:
|
||||
_in: X-Hasura-Conference-Ids
|
||||
- subconferenceId:
|
||||
_in: X-Hasura-Subconference-Ids
|
||||
custom_sql: null
|
||||
functions:
|
||||
- function:
|
||||
schema: collection
|
||||
|
@ -706,6 +706,7 @@ library
|
||||
, Hasura.Server.Telemetry.Types
|
||||
, Hasura.Server.Telemetry.Counters
|
||||
, Hasura.Server.Auth.JWT
|
||||
, Hasura.CustomSQL
|
||||
, Hasura.GC
|
||||
, Hasura.Incremental.Internal.Cache
|
||||
, Hasura.Incremental.Internal.Dependency
|
||||
@ -793,6 +794,7 @@ library
|
||||
, Hasura.RQL.DDL.Headers
|
||||
, Hasura.RQL.DDL.Metadata
|
||||
, Hasura.RQL.DDL.Metadata.Types
|
||||
, Hasura.RQL.DDL.CustomSQL
|
||||
, Hasura.RQL.DDL.OpenTelemetry
|
||||
, Hasura.RQL.DDL.Permission
|
||||
, Hasura.RQL.DDL.Permission.Internal
|
||||
|
@ -74,6 +74,7 @@ library
|
||||
Test.API.Metadata.SuggestRelationshipsSpec
|
||||
Test.API.Metadata.InconsistentSpec
|
||||
Test.API.Metadata.TransparentDefaultsSpec
|
||||
Test.API.Metadata.NativeAccessSpec
|
||||
Test.API.Schema.RunSQLSpec
|
||||
Test.Auth.Authorization.DisableRootFields.Common
|
||||
Test.Auth.Authorization.DisableRootFields.DefaultRootFieldsSpec
|
||||
|
197
server/lib/api-tests/src/Test/API/Metadata/NativeAccessSpec.hs
Normal file
197
server/lib/api-tests/src/Test/API/Metadata/NativeAccessSpec.hs
Normal file
@ -0,0 +1,197 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
-- | Access to the SQL
|
||||
module Test.API.Metadata.NativeAccessSpec (spec) where
|
||||
|
||||
import Data.List.NonEmpty qualified as NE
|
||||
import Harness.Backend.Postgres qualified as Postgres
|
||||
import Harness.GraphqlEngine qualified as GraphqlEngine
|
||||
import Harness.Quoter.Yaml (yaml)
|
||||
import Harness.Test.Fixture qualified as Fixture
|
||||
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
|
||||
import Harness.Yaml (shouldReturnYaml)
|
||||
import Hasura.Prelude
|
||||
import Test.Hspec (SpecWith, it)
|
||||
|
||||
-- ** Preamble
|
||||
|
||||
--
|
||||
-- We currently don't need the table to exist in order to set up a custom SQL
|
||||
-- stanza.
|
||||
|
||||
spec :: SpecWith GlobalTestEnvironment
|
||||
spec =
|
||||
Fixture.run
|
||||
( NE.fromList
|
||||
[ (Fixture.fixture $ Fixture.Backend Postgres.backendTypeMetadata)
|
||||
{ Fixture.setupTeardown = \(testEnv, _) ->
|
||||
[ Postgres.setupTablesAction [] testEnv
|
||||
],
|
||||
Fixture.customOptions =
|
||||
Just $
|
||||
Fixture.defaultOptions
|
||||
{ Fixture.skipTests =
|
||||
Just "Disabled until we can dynamically switch on Native Access with a command line option in NDAT-452"
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
tests
|
||||
|
||||
-- ** Setup and teardown
|
||||
|
||||
tests :: Fixture.Options -> SpecWith TestEnvironment
|
||||
tests opts = do
|
||||
let query :: Text
|
||||
query = "SELECT thing / {{denominator}} AS divided FROM stuff WHERE date = {{target_date}}"
|
||||
|
||||
it "Adds a native access function and returns a 200" $ \testEnv -> do
|
||||
shouldReturnYaml
|
||||
opts
|
||||
( GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_track_custom_sql
|
||||
args:
|
||||
type: query
|
||||
source: postgres
|
||||
root_field_name: divided_stuff
|
||||
sql: *query
|
||||
parameters:
|
||||
- name: denominator
|
||||
type: int
|
||||
- name: target_date
|
||||
type: date
|
||||
returns: already_tracked_return_type
|
||||
|]
|
||||
)
|
||||
[yaml|
|
||||
message: success
|
||||
|]
|
||||
|
||||
it "Checks for the native access function" $ \testEnv -> do
|
||||
shouldReturnYaml
|
||||
opts
|
||||
( GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_track_custom_sql
|
||||
args:
|
||||
type: query
|
||||
source: postgres
|
||||
root_field_name: divided_stuff
|
||||
sql: *query
|
||||
parameters:
|
||||
- name: denominator
|
||||
type: int
|
||||
- name: target_date
|
||||
type: date
|
||||
returns: already_tracked_return_type
|
||||
|]
|
||||
)
|
||||
[yaml|
|
||||
message: success
|
||||
|]
|
||||
|
||||
shouldReturnYaml
|
||||
opts
|
||||
( GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_get_custom_sql
|
||||
args:
|
||||
source: postgres
|
||||
|]
|
||||
)
|
||||
[yaml|
|
||||
divided_stuff:
|
||||
type: query
|
||||
root_field_name: divided_stuff
|
||||
sql: *query
|
||||
parameters:
|
||||
- name: denominator
|
||||
type: int
|
||||
- name: target_date
|
||||
type: date
|
||||
returns:
|
||||
name: already_tracked_return_type
|
||||
schema: public
|
||||
|
||||
|]
|
||||
|
||||
it "Drops a native access function and returns a 200" $ \testEnv -> do
|
||||
_ <-
|
||||
GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_track_custom_sql
|
||||
args:
|
||||
type: query
|
||||
source: postgres
|
||||
root_field_name: divided_stuff
|
||||
sql: *query
|
||||
parameters:
|
||||
- name: denominator
|
||||
type: int
|
||||
- name: target_date
|
||||
type: date
|
||||
returns: already_tracked_return_type
|
||||
|]
|
||||
|
||||
shouldReturnYaml
|
||||
opts
|
||||
( GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_untrack_custom_sql
|
||||
args:
|
||||
source: postgres
|
||||
root_field_name: divided_stuff
|
||||
|]
|
||||
)
|
||||
[yaml|
|
||||
message: success
|
||||
|]
|
||||
|
||||
it "Checks the native access function can be deleted" $ \testEnv -> do
|
||||
_ <-
|
||||
GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_track_custom_sql
|
||||
args:
|
||||
type: query
|
||||
source: postgres
|
||||
root_field_name: divided_stuff
|
||||
sql: *query
|
||||
parameters:
|
||||
- name: denominator
|
||||
type: int
|
||||
- name: target_date
|
||||
type: date
|
||||
returns: already_tracked_return_type
|
||||
|]
|
||||
|
||||
_ <-
|
||||
GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_untrack_custom_sql
|
||||
args:
|
||||
root_field_name: divided_stuff
|
||||
source: postgres
|
||||
|]
|
||||
|
||||
shouldReturnYaml
|
||||
opts
|
||||
( GraphqlEngine.postMetadata
|
||||
testEnv
|
||||
[yaml|
|
||||
type: pg_get_custom_sql
|
||||
args:
|
||||
source: postgres
|
||||
|]
|
||||
)
|
||||
[yaml|
|
||||
{}
|
||||
|]
|
@ -58,6 +58,7 @@ library
|
||||
build-depends:
|
||||
, aeson >=1.5
|
||||
, attoparsec >=0.14
|
||||
, autodocodec
|
||||
, base >=4.7
|
||||
, bytestring >=0.10
|
||||
, deepseq >=1.4
|
||||
|
@ -24,6 +24,7 @@ where
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
import Autodocodec (HasCodec (codec), bimapCodec)
|
||||
import Control.DeepSeq (NFData)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Char qualified as C
|
||||
@ -31,6 +32,7 @@ import Data.Coerce (coerce)
|
||||
import Data.Hashable (Hashable)
|
||||
import Data.Text (Text)
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Instances.TH.Lift ()
|
||||
import Language.Haskell.TH.Syntax (Lift)
|
||||
import Language.Haskell.TH.Syntax.Compat (SpliceQ, examineSplice, liftSplice)
|
||||
@ -44,6 +46,9 @@ newtype Name = Name {unName :: Text}
|
||||
deriving stock (Eq, Lift, Ord, Show)
|
||||
deriving newtype (Semigroup, Hashable, NFData, Pretty, J.ToJSONKey, J.ToJSON)
|
||||
|
||||
instance HasCodec Name where
|
||||
codec = bimapCodec (J.eitherDecodeStrict . encodeUtf8) unName codec
|
||||
|
||||
-- | @NameSuffix@ is essentially a GQL identifier that can be used as Suffix
|
||||
-- It is slightely different from @Name@ as it relaxes the criteria that a
|
||||
-- @Name@ cannot start with a digit.
|
||||
|
@ -860,7 +860,7 @@ mkHGEServer setupHook env ServeOptions {..} serverCtx@ServerCtx {..} ekgStore =
|
||||
-- event triggers should be tied to the life cycle of a source
|
||||
lockedEvents <- readTVarIO leEvents
|
||||
forM_ sources $ \backendSourceInfo -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo sourceName _ _ sourceConfig _ _ :: SourceInfo b) -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo sourceName _ _ _ sourceConfig _ _ :: SourceInfo b) -> do
|
||||
let sourceNameText = sourceNameToText sourceName
|
||||
logger $ mkGenericLog LevelInfo "event_triggers" $ "unlocking events of source: " <> sourceNameText
|
||||
for_ (HM.lookup sourceName lockedEvents) $ \sourceLockedEvents -> do
|
||||
|
@ -74,7 +74,7 @@ runSQL ::
|
||||
MSSQLRunSQL ->
|
||||
m EncJSON
|
||||
runSQL mssqlRunSQL@MSSQLRunSQL {..} = do
|
||||
SourceInfo _ tableCache _ sourceConfig _ _ <- askSourceInfo @'MSSQL _mrsSource
|
||||
SourceInfo _ tableCache _ _ sourceConfig _ _ <- askSourceInfo @'MSSQL _mrsSource
|
||||
results <-
|
||||
-- If the SQL modifies the schema of the database then check for any metadata changes
|
||||
if isSchemaCacheBuildRequiredRunSQL mssqlRunSQL
|
||||
|
@ -256,7 +256,7 @@ withMetadataCheck ::
|
||||
PG.TxET QErr m a ->
|
||||
m a
|
||||
withMetadataCheck source cascade txAccess runSQLQuery = do
|
||||
SourceInfo _ tableCache functionCache sourceConfig _ _ <- askSourceInfo @('Postgres pgKind) source
|
||||
SourceInfo _ tableCache functionCache _customSQL sourceConfig _ _ <- askSourceInfo @('Postgres pgKind) source
|
||||
|
||||
-- Run SQL query and metadata checker in a transaction
|
||||
(queryResult, metadataUpdater) <- runTxWithMetadataCheck source sourceConfig txAccess tableCache functionCache cascade runSQLQuery
|
||||
|
@ -176,6 +176,7 @@ resolveDatabaseMetadata sourceMetadata sourceConfig sourceCustomization = runExc
|
||||
name <- afold @(Either QErr) $ mkScalarTypeName scalar
|
||||
pure (name, scalar)
|
||||
pure (tablesMeta, functionsMeta, scalarsMap)
|
||||
|
||||
pure $ ResolvedSource sourceConfig sourceCustomization tablesMeta functionsMeta (ScalarMap pgScalars)
|
||||
where
|
||||
-- A helper function to list all functions underpinning computed fields from a table metadata
|
||||
|
@ -23,6 +23,7 @@ instance BackendAPI ('Postgres 'Vanilla) where
|
||||
remoteRelationshipCommands @('Postgres 'Vanilla),
|
||||
eventTriggerCommands @('Postgres 'Vanilla),
|
||||
computedFieldCommands @('Postgres 'Vanilla),
|
||||
nativeAccessCommands @('Postgres 'Vanilla),
|
||||
[ commandParser
|
||||
"set_table_is_enum"
|
||||
( RMPgSetTableIsEnum
|
||||
|
52
server/src-lib/Hasura/CustomSQL.hs
Normal file
52
server/src-lib/Hasura/CustomSQL.hs
Normal file
@ -0,0 +1,52 @@
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
|
||||
-- | Types concerned with user-specified custom SQL fragments.
|
||||
module Hasura.CustomSQL
|
||||
( CustomSQLParameter (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Autodocodec (HasCodec (codec))
|
||||
import Autodocodec qualified as AC
|
||||
import Data.Aeson
|
||||
import Hasura.Prelude
|
||||
|
||||
----------------------------------
|
||||
|
||||
newtype CustomSQLParameterName = CustomSQLParameterName {cspnName :: Text}
|
||||
deriving newtype (Show, Eq, FromJSON, ToJSON)
|
||||
|
||||
instance HasCodec CustomSQLParameterName where
|
||||
codec = AC.dimapCodec CustomSQLParameterName cspnName codec
|
||||
|
||||
newtype CustomSQLParameterType = CustomSQLParameterType {cspnType :: Text}
|
||||
deriving newtype (Show, Eq, FromJSON, ToJSON)
|
||||
|
||||
instance HasCodec CustomSQLParameterType where
|
||||
codec = AC.dimapCodec CustomSQLParameterType cspnType codec
|
||||
|
||||
data CustomSQLParameter = CustomSQLParameter
|
||||
{ cspName :: CustomSQLParameterName,
|
||||
cspType :: CustomSQLParameterType
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance FromJSON CustomSQLParameter where
|
||||
parseJSON = withObject "CustomSQLParameter" $ \o -> do
|
||||
cspName <- o .: "name"
|
||||
cspType <- o .: "type"
|
||||
pure CustomSQLParameter {..}
|
||||
|
||||
instance ToJSON CustomSQLParameter where
|
||||
toJSON CustomSQLParameter {..} =
|
||||
object
|
||||
[ "name" .= cspName,
|
||||
"type" .= cspType
|
||||
]
|
||||
|
||||
instance HasCodec CustomSQLParameter where
|
||||
codec =
|
||||
AC.object "CustomSQLParameter" $
|
||||
CustomSQLParameter
|
||||
<$> AC.requiredField' "name" AC..= cspName
|
||||
<*> AC.requiredField' "type" AC..= cspType
|
@ -248,7 +248,7 @@ processEventQueue logger httpMgr getSchemaCache EventEngineCtx {..} LockedEvents
|
||||
liftIO . fmap concat $
|
||||
-- fetch pending events across all the sources asynchronously
|
||||
LA.forConcurrently (M.toList allSources) \(sourceName, sourceCache) ->
|
||||
AB.dispatchAnyBackend @BackendEventTrigger sourceCache \(SourceInfo _sourceName tableCache _functionCache sourceConfig _queryTagsConfig _sourceCustomization :: SourceInfo b) -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger sourceCache \(SourceInfo _sourceName tableCache _functionCache _customSQLCache sourceConfig _queryTagsConfig _sourceCustomization :: SourceInfo b) -> do
|
||||
let tables = M.elems tableCache
|
||||
triggerMap = _tiEventTriggerInfoMap <$> tables
|
||||
eventTriggerCount = sum (M.size <$> triggerMap)
|
||||
|
@ -328,7 +328,7 @@ buildRoleContext options sources remotes actions customTypes role remoteSchemaPe
|
||||
[FieldParser P.Parse (NamespacedField (QueryRootField UnpreparedValue))],
|
||||
[(G.Name, Parser 'Output P.Parse (ApolloFederationParserFunction P.Parse))]
|
||||
)
|
||||
buildSource schemaContext schemaOptions sourceInfo@(SourceInfo _ tables functions _ _ sourceCustomization) =
|
||||
buildSource schemaContext schemaOptions sourceInfo@(SourceInfo _ tables functions _customSQL _ _ sourceCustomization) =
|
||||
runSourceSchema schemaContext schemaOptions sourceInfo do
|
||||
let validFunctions = takeValidFunctions functions
|
||||
validTables = takeValidTables tables
|
||||
@ -453,7 +453,7 @@ buildRelayRoleContext options sources actions customTypes role expFeatures = do
|
||||
[FieldParser P.Parse (NamespacedField (MutationRootField UnpreparedValue))],
|
||||
[FieldParser P.Parse (NamespacedField (QueryRootField UnpreparedValue))]
|
||||
)
|
||||
buildSource schemaContext schemaOptions sourceInfo@(SourceInfo _ tables functions _ _ sourceCustomization) = do
|
||||
buildSource schemaContext schemaOptions sourceInfo@(SourceInfo _ tables functions _customSQL _ _ sourceCustomization) = do
|
||||
runSourceSchema schemaContext schemaOptions sourceInfo do
|
||||
let validFunctions = takeValidFunctions functions
|
||||
validTables = takeValidTables tables
|
||||
|
211
server/src-lib/Hasura/RQL/DDL/CustomSQL.hs
Normal file
211
server/src-lib/Hasura/RQL/DDL/CustomSQL.hs
Normal file
@ -0,0 +1,211 @@
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
|
||||
-- | Metadata V1 commands (and their types) for handling user-specified custom
|
||||
-- SQL fragments.
|
||||
module Hasura.RQL.DDL.CustomSQL
|
||||
( GetCustomSQL (..),
|
||||
TrackCustomSQL (..),
|
||||
UntrackCustomSQL (..),
|
||||
runGetCustomSQL,
|
||||
runTrackCustomSQL,
|
||||
runUntrackCustomSQL,
|
||||
dropCustomSQLInMetadata,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Lens ((^?))
|
||||
import Data.Aeson
|
||||
import Data.HashMap.Strict.InsOrd qualified as OMap
|
||||
import Hasura.Base.Error
|
||||
import Hasura.CustomSQL
|
||||
import Hasura.EncJSON
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Backend (Backend, TableName)
|
||||
import Hasura.RQL.Types.Common (SourceName, successMsg)
|
||||
import Hasura.RQL.Types.Metadata
|
||||
import Hasura.RQL.Types.Metadata.Backend
|
||||
import Hasura.RQL.Types.Metadata.Object
|
||||
import Hasura.RQL.Types.SchemaCache.Build
|
||||
import Hasura.SQL.AnyBackend qualified as AB
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.Server.Types (HasServerConfigCtx (..), ServerConfigCtx (..))
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
|
||||
---------------------------------
|
||||
|
||||
data GetCustomSQL (b :: BackendType) = GetCustomSQL
|
||||
{ gcsSource :: SourceName
|
||||
}
|
||||
|
||||
deriving instance Backend b => Show (GetCustomSQL b)
|
||||
|
||||
deriving instance Backend b => Eq (GetCustomSQL b)
|
||||
|
||||
instance Backend b => FromJSON (GetCustomSQL b) where
|
||||
parseJSON = withObject "GetCustomSQL" $ \o -> do
|
||||
gcsSource <- o .: "source"
|
||||
pure GetCustomSQL {..}
|
||||
|
||||
instance Backend b => ToJSON (GetCustomSQL b) where
|
||||
toJSON GetCustomSQL {..} =
|
||||
object
|
||||
[ "source" .= gcsSource
|
||||
]
|
||||
|
||||
runGetCustomSQL ::
|
||||
forall b m.
|
||||
( BackendMetadata b,
|
||||
MetadataM m,
|
||||
HasServerConfigCtx m,
|
||||
MonadIO m,
|
||||
MonadError QErr m
|
||||
) =>
|
||||
GetCustomSQL b ->
|
||||
m EncJSON
|
||||
runGetCustomSQL q = do
|
||||
throwIfFeatureDisabled
|
||||
|
||||
metadata <- getMetadata
|
||||
|
||||
let customSQL :: Maybe (CustomSQLFields b)
|
||||
customSQL = metadata ^? metaSources . ix (gcsSource q) . toSourceMetadata . smCustomSQL
|
||||
|
||||
pure (encJFromJValue customSQL)
|
||||
|
||||
----------------------------------
|
||||
|
||||
data TrackCustomSQL (b :: BackendType) = TrackCustomSQL
|
||||
{ tcsSource :: SourceName,
|
||||
tcsType :: Text,
|
||||
tcsRootFieldName :: G.Name,
|
||||
tcsSql :: Text,
|
||||
tcsParameters :: NonEmpty CustomSQLParameter,
|
||||
tcsReturns :: TableName b
|
||||
}
|
||||
|
||||
deriving instance Backend b => Show (TrackCustomSQL b)
|
||||
|
||||
deriving instance Backend b => Eq (TrackCustomSQL b)
|
||||
|
||||
instance Backend b => FromJSON (TrackCustomSQL b) where
|
||||
parseJSON = withObject "TrackCustomSQL" $ \o -> do
|
||||
tcsSource <- o .: "source"
|
||||
tcsType <- o .: "type"
|
||||
tcsRootFieldName <- o .: "root_field_name"
|
||||
tcsSql <- o .: "sql"
|
||||
tcsParameters <- o .: "parameters"
|
||||
tcsReturns <- o .: "returns"
|
||||
pure TrackCustomSQL {..}
|
||||
|
||||
instance Backend b => ToJSON (TrackCustomSQL b) where
|
||||
toJSON TrackCustomSQL {..} =
|
||||
object
|
||||
[ "source" .= tcsSource,
|
||||
"type" .= tcsType,
|
||||
"root_field_name" .= tcsRootFieldName,
|
||||
"sql" .= tcsSql,
|
||||
"parameters" .= tcsParameters,
|
||||
"returns" .= tcsReturns
|
||||
]
|
||||
|
||||
runTrackCustomSQL ::
|
||||
forall b m.
|
||||
( BackendMetadata b,
|
||||
CacheRWM m,
|
||||
MetadataM m,
|
||||
MonadError QErr m,
|
||||
HasServerConfigCtx m,
|
||||
MonadIO m
|
||||
) =>
|
||||
TrackCustomSQL b ->
|
||||
m EncJSON
|
||||
runTrackCustomSQL q = do
|
||||
throwIfFeatureDisabled
|
||||
|
||||
let metadataObj =
|
||||
MOSourceObjId source $
|
||||
AB.mkAnyBackend $
|
||||
SMOCustomSQL @b fieldName
|
||||
metadata =
|
||||
CustomSQLMetadata
|
||||
{ _csmType = tcsType q,
|
||||
_csmRootFieldName = tcsRootFieldName q,
|
||||
_csmSql = tcsSql q,
|
||||
_csmParameters = tcsParameters q,
|
||||
_csmReturns = tcsReturns q
|
||||
}
|
||||
|
||||
buildSchemaCacheFor metadataObj $
|
||||
MetadataModifier $
|
||||
(metaSources . ix source . toSourceMetadata @b . smCustomSQL)
|
||||
%~ OMap.insert fieldName metadata
|
||||
|
||||
pure successMsg
|
||||
where
|
||||
source = tcsSource q
|
||||
fieldName = tcsRootFieldName q
|
||||
|
||||
---------------------------------
|
||||
|
||||
data UntrackCustomSQL (b :: BackendType) = UntrackCustomSQL
|
||||
{ utcsSource :: SourceName,
|
||||
utcsRootFieldName :: G.Name
|
||||
}
|
||||
|
||||
deriving instance Backend b => Show (UntrackCustomSQL b)
|
||||
|
||||
deriving instance Backend b => Eq (UntrackCustomSQL b)
|
||||
|
||||
instance Backend b => FromJSON (UntrackCustomSQL b) where
|
||||
parseJSON = withObject "UntrackCustomSQL" $ \o -> do
|
||||
utcsSource <- o .: "source"
|
||||
utcsRootFieldName <- o .: "root_field_name"
|
||||
pure UntrackCustomSQL {..}
|
||||
|
||||
instance Backend b => ToJSON (UntrackCustomSQL b) where
|
||||
toJSON UntrackCustomSQL {..} =
|
||||
object
|
||||
[ "source" .= utcsSource,
|
||||
"root_field_name" .= utcsRootFieldName
|
||||
]
|
||||
|
||||
runUntrackCustomSQL ::
|
||||
forall b m.
|
||||
( BackendMetadata b,
|
||||
MonadError QErr m,
|
||||
CacheRWM m,
|
||||
MetadataM m,
|
||||
HasServerConfigCtx m,
|
||||
MonadIO m
|
||||
) =>
|
||||
UntrackCustomSQL b ->
|
||||
m EncJSON
|
||||
runUntrackCustomSQL q = do
|
||||
throwIfFeatureDisabled
|
||||
|
||||
let metadataObj =
|
||||
MOSourceObjId source $
|
||||
AB.mkAnyBackend $
|
||||
SMOCustomSQL @b fieldName
|
||||
|
||||
buildSchemaCacheFor metadataObj $
|
||||
dropCustomSQLInMetadata @b source (fieldName :: G.Name)
|
||||
|
||||
pure successMsg
|
||||
where
|
||||
source = utcsSource q
|
||||
fieldName = utcsRootFieldName q
|
||||
|
||||
dropCustomSQLInMetadata :: forall b. BackendMetadata b => SourceName -> G.Name -> MetadataModifier
|
||||
dropCustomSQLInMetadata source rootFieldName =
|
||||
MetadataModifier $
|
||||
metaSources . ix source . toSourceMetadata @b . smCustomSQL %~ OMap.delete rootFieldName
|
||||
|
||||
-- | check feature flag is enabled before carrying out any actions
|
||||
throwIfFeatureDisabled :: (HasServerConfigCtx m, MonadIO m, MonadError QErr m) => m ()
|
||||
throwIfFeatureDisabled = do
|
||||
configCtx <- askServerConfigCtx
|
||||
|
||||
enableCustomSQL <- liftIO (_sccUsePQNP configCtx)
|
||||
|
||||
unless enableCustomSQL (throw500 "CustomSQL is disabled!")
|
@ -605,7 +605,7 @@ toggleEventTriggerCleanupAction conf cleanupSwitch = do
|
||||
case tlcs of
|
||||
TriggerAllSource -> do
|
||||
ifor_ (scSources schemaCache) $ \sourceName backendSourceInfo -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo _ tableCache _ _ _ _ :: SourceInfo b) -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo _ tableCache _ _customSQLCache _ _ _ :: SourceInfo b) -> do
|
||||
traverseTableHelper tableCache cleanupSwitch sourceName
|
||||
TriggerSource sourceNameLst -> do
|
||||
forM_ sourceNameLst $ \sourceName -> do
|
||||
@ -613,7 +613,7 @@ toggleEventTriggerCleanupAction conf cleanupSwitch = do
|
||||
HM.lookup sourceName (scSources schemaCache)
|
||||
`onNothing` throw400 NotExists ("source with name " <> sourceNameToText sourceName <> " does not exists")
|
||||
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo _ tableCache _ _ _ _ :: SourceInfo b) -> do
|
||||
AB.dispatchAnyBackend @BackendEventTrigger backendSourceInfo \(SourceInfo _ tableCache _ _customSQLCache _ _ _ :: SourceInfo b) -> do
|
||||
traverseTableHelper tableCache cleanupSwitch sourceName
|
||||
TriggerQualifier qualifierLst -> do
|
||||
forM_ qualifierLst $ \qualifier -> do
|
||||
|
@ -46,6 +46,7 @@ import Hasura.Metadata.Class
|
||||
import Hasura.Prelude hiding (first)
|
||||
import Hasura.RQL.DDL.Action
|
||||
import Hasura.RQL.DDL.ComputedField
|
||||
import Hasura.RQL.DDL.CustomSQL (dropCustomSQLInMetadata)
|
||||
import Hasura.RQL.DDL.CustomTypes
|
||||
import Hasura.RQL.DDL.Endpoint
|
||||
import Hasura.RQL.DDL.EventTrigger
|
||||
@ -142,6 +143,7 @@ runClearMetadata _ = do
|
||||
(_smKind @b s)
|
||||
mempty
|
||||
mempty
|
||||
mempty
|
||||
(_smConfiguration @b s)
|
||||
Nothing
|
||||
emptySourceCustomization
|
||||
@ -514,7 +516,7 @@ runReplaceMetadataV2 ReplaceMetadataV2 {..} = do
|
||||
<<> "' as it is inconsistent"
|
||||
)
|
||||
J.Null
|
||||
Just sourceInfo@(SourceInfo _ _ _ sourceConfig _ _) -> do
|
||||
Just sourceInfo@(SourceInfo _ _ _ _ sourceConfig _ _) -> do
|
||||
let getEventMapWithCC sourceMeta = Map.fromList $ concatMap (getAllETWithCleanupConfigInTableMetadata . snd) $ OMap.toList $ _smTables sourceMeta
|
||||
oldEventTriggersWithCC = getEventMapWithCC oldSourceMetadata
|
||||
newEventTriggersWithCC = getEventMapWithCC newSourceMetadata
|
||||
@ -689,6 +691,7 @@ purgeMetadataObj = \case
|
||||
SMOTable qt -> dropTableInMetadata @b source qt
|
||||
SMOFunction qf -> dropFunctionInMetadata @b source qf
|
||||
SMOFunctionPermission qf rn -> dropFunctionPermissionInMetadata @b source qf rn
|
||||
SMOCustomSQL qc -> dropCustomSQLInMetadata @b source qc
|
||||
SMOTableObj qt tableObj ->
|
||||
MetadataModifier $
|
||||
tableMetadataSetter @b source qt %~ case tableObj of
|
||||
|
@ -608,7 +608,7 @@ buildSchemaCacheRule logger env = proc (metadataNoDefaults, invalidationKeys) ->
|
||||
)
|
||||
`arr` (SourceInfo b)
|
||||
buildSource = proc (allSources, sourceMetadata, sourceConfig, tablesRawInfo, eventTriggerInfoMaps, _dbTables, dbFunctions, remoteSchemaMap, orderedRoles) -> do
|
||||
let SourceMetadata sourceName _backendKind tables functions _ queryTagsConfig sourceCustomization _healthCheckConfig = sourceMetadata
|
||||
let SourceMetadata sourceName _backendKind tables functions customSQL _ queryTagsConfig sourceCustomization _healthCheckConfig = sourceMetadata
|
||||
tablesMetadata = OMap.elems tables
|
||||
(_, nonColumnInputs, permissions) = unzip3 $ map mkTableInputs tablesMetadata
|
||||
alignTableMap :: HashMap (TableName b) a -> HashMap (TableName b) c -> HashMap (TableName b) (a, c)
|
||||
@ -687,9 +687,10 @@ buildSchemaCacheRule logger env = proc (metadataNoDefaults, invalidationKeys) ->
|
||||
(functionInfo, dep) <- buildFunctionInfo sourceName qf systemDefined config permissionsMap rawfunctionInfo comment namingConv
|
||||
recordDependenciesM metadataObject schemaObject (Seq.singleton dep)
|
||||
pure functionInfo
|
||||
|
||||
let functionCache = mapFromL _fiSQLName $ catMaybes functionCacheMaybes
|
||||
|
||||
returnA -< SourceInfo sourceName tableCache functionCache sourceConfig queryTagsConfig resolvedCustomization
|
||||
returnA -< SourceInfo sourceName tableCache functionCache customSQL sourceConfig queryTagsConfig resolvedCustomization
|
||||
|
||||
buildAndCollectInfo ::
|
||||
forall arr m.
|
||||
@ -719,7 +720,7 @@ buildSchemaCacheRule logger env = proc (metadataNoDefaults, invalidationKeys) ->
|
||||
HS.fromList $
|
||||
concat $
|
||||
OMap.elems sources >>= \(BackendSourceMetadata e) ->
|
||||
AB.dispatchAnyBackend @Backend e \(SourceMetadata _ _ tables _functions _ _ _ _) -> do
|
||||
AB.dispatchAnyBackend @Backend e \(SourceMetadata _ _ tables _functions _customSQL _ _ _ _) -> do
|
||||
table <- OMap.elems tables
|
||||
pure $
|
||||
OMap.keys (_tmInsertPermissions table)
|
||||
|
@ -273,6 +273,7 @@ deleteMetadataObject = \case
|
||||
SMOFunction name -> siFunctions %~ M.delete name
|
||||
SMOFunctionPermission functionName role ->
|
||||
siFunctions . ix functionName . fiPermissions %~ M.delete role
|
||||
SMOCustomSQL name -> siCustomSQL %~ OMap.delete name
|
||||
SMOTableObj tableName tableObjectId ->
|
||||
siTables . ix tableName %~ case tableObjectId of
|
||||
MTORel name _ -> tiCoreInfo . tciFieldInfoMap %~ M.delete (fromRel name)
|
||||
|
@ -14,6 +14,8 @@ module Hasura.RQL.Types.Metadata.Common
|
||||
ComputedFieldMetadata (..),
|
||||
ComputedFields,
|
||||
CronTriggers,
|
||||
CustomSQLFields,
|
||||
CustomSQLMetadata (..),
|
||||
Endpoints,
|
||||
EventTriggers,
|
||||
FunctionMetadata (..),
|
||||
@ -46,6 +48,7 @@ module Hasura.RQL.Types.Metadata.Common
|
||||
smQueryTags,
|
||||
smTables,
|
||||
smCustomization,
|
||||
smCustomSQL,
|
||||
smHealthCheckConfig,
|
||||
sourcesCodec,
|
||||
tmArrayRelationships,
|
||||
@ -61,6 +64,11 @@ module Hasura.RQL.Types.Metadata.Common
|
||||
tmSelectPermissions,
|
||||
tmTable,
|
||||
tmUpdatePermissions,
|
||||
csmParameters,
|
||||
csmReturns,
|
||||
csmRootFieldName,
|
||||
csmSql,
|
||||
csmType,
|
||||
toSourceMetadata,
|
||||
)
|
||||
where
|
||||
@ -79,6 +87,7 @@ import Data.List.Extended qualified as L
|
||||
import Data.Maybe (fromJust)
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Extended qualified as T
|
||||
import Hasura.CustomSQL (CustomSQLParameter)
|
||||
import Hasura.Metadata.DTO.Placeholder (placeholderCodecViaJSON)
|
||||
import Hasura.Metadata.DTO.Utils (codecNamePrefix)
|
||||
import Hasura.Prelude
|
||||
@ -108,6 +117,7 @@ import Hasura.SQL.AnyBackend qualified as AB
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.SQL.Tag (BackendTag, HasTag (backendTag))
|
||||
import Hasura.Session
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
|
||||
-- | Parse a list of objects into a map from a derived key,
|
||||
-- failing if the list has duplicates.
|
||||
@ -359,6 +369,59 @@ instance (Backend b) => HasCodec (FunctionMetadata b) where
|
||||
nameDoc = "Name of the SQL function"
|
||||
configDoc = "Configuration for the SQL function"
|
||||
|
||||
-- | This is everything we are passed from the metadata call currently,
|
||||
-- there is probably some sort of checking/refinement that needs to happen
|
||||
-- before we store it, but for now, YOLO.
|
||||
data CustomSQLMetadata b = CustomSQLMetadata
|
||||
{ _csmType :: Text,
|
||||
_csmRootFieldName :: G.Name,
|
||||
_csmSql :: Text,
|
||||
_csmParameters :: NonEmpty CustomSQLParameter,
|
||||
_csmReturns :: TableName b
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
deriving instance (Backend b) => Show (CustomSQLMetadata b)
|
||||
|
||||
deriving instance (Backend b) => Eq (CustomSQLMetadata b)
|
||||
|
||||
instance (Backend b) => ToJSON (CustomSQLMetadata b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
|
||||
$(makeLenses ''CustomSQLMetadata)
|
||||
|
||||
instance (Backend b) => FromJSON (CustomSQLMetadata b) where
|
||||
parseJSON = withObject "CustomSQLMetadata" $ \o ->
|
||||
CustomSQLMetadata
|
||||
<$> o .: "type"
|
||||
<*> o .: "root_field_name"
|
||||
<*> o .: "sql"
|
||||
<*> o .: "parameters"
|
||||
<*> o .: "returns"
|
||||
|
||||
instance (Backend b) => HasCodec (CustomSQLMetadata b) where
|
||||
codec =
|
||||
CommentCodec
|
||||
( T.unlines
|
||||
[ "A custom SQL function to add to the GraphQL schema with configuration.",
|
||||
"",
|
||||
"https://hasura.io/docs/latest/graphql/core/api-reference/schema-metadata-api/custom-functions.html#args-syntax"
|
||||
]
|
||||
)
|
||||
$ AC.object (codecNamePrefix @b <> "CustomSQLMetadata")
|
||||
$ CustomSQLMetadata
|
||||
<$> requiredField "type" typeDoc AC..= _csmType
|
||||
<*> requiredField "root_field_name" fieldNameDoc AC..= _csmRootFieldName
|
||||
<*> requiredField "sql" sqlDoc AC..= _csmSql
|
||||
<*> requiredField "parameters" paramDoc AC..= _csmParameters
|
||||
<*> requiredField "returns" returnsDoc AC..= _csmReturns
|
||||
where
|
||||
typeDoc = "Type of SQL statement to run"
|
||||
fieldNameDoc = "Field name for custom SQL"
|
||||
sqlDoc = "SQL to run"
|
||||
paramDoc = "Function parameters and their types"
|
||||
returnsDoc = "Return type of function"
|
||||
|
||||
type RemoteSchemaMetadata = RemoteSchemaMetadataG RemoteRelationshipDefinition
|
||||
|
||||
type RemoteSchemas = InsOrdHashMap RemoteSchemaName RemoteSchemaMetadata
|
||||
@ -367,6 +430,8 @@ type Tables b = InsOrdHashMap (TableName b) (TableMetadata b)
|
||||
|
||||
type Functions b = InsOrdHashMap (FunctionName b) (FunctionMetadata b)
|
||||
|
||||
type CustomSQLFields b = InsOrdHashMap G.Name (CustomSQLMetadata b)
|
||||
|
||||
type Endpoints = InsOrdHashMap EndpointName CreateEndpoint
|
||||
|
||||
type Actions = InsOrdHashMap ActionName ActionMetadata
|
||||
@ -381,6 +446,7 @@ data SourceMetadata b = SourceMetadata
|
||||
_smKind :: BackendSourceKind b,
|
||||
_smTables :: Tables b,
|
||||
_smFunctions :: Functions b,
|
||||
_smCustomSQL :: CustomSQLFields b,
|
||||
_smConfiguration :: SourceConnConfiguration b,
|
||||
_smQueryTags :: Maybe QueryTagsConfig,
|
||||
_smCustomization :: SourceCustomization,
|
||||
@ -399,6 +465,7 @@ instance (Backend b) => FromJSONWithContext (BackendSourceKind b) (SourceMetadat
|
||||
_smName <- o .: "name"
|
||||
_smTables <- oMapFromL _tmTable <$> o .: "tables"
|
||||
_smFunctions <- oMapFromL _fmFunction <$> o .:? "functions" .!= []
|
||||
_smCustomSQL <- oMapFromL _csmRootFieldName <$> o .:? "custom_sql" .!= []
|
||||
_smConfiguration <- o .: "configuration"
|
||||
_smQueryTags <- o .:? "query_tags"
|
||||
_smCustomization <- o .:? "customization" .!= emptySourceCustomization
|
||||
@ -453,6 +520,7 @@ instance Backend b => HasCodec (SourceMetadata b) where
|
||||
<*> requiredField' "kind" .== _smKind
|
||||
<*> requiredFieldWith' "tables" (sortedElemsCodec _tmTable) .== _smTables
|
||||
<*> optionalFieldOrNullWithOmittedDefaultWith' "functions" (sortedElemsCodec _fmFunction) mempty .== _smFunctions
|
||||
<*> optionalFieldOrNullWithOmittedDefaultWith' "custom_sql" (sortedElemsCodec _csmRootFieldName) mempty .== _smCustomSQL
|
||||
<*> requiredField' "configuration" .== _smConfiguration
|
||||
<*> optionalFieldOrNull' "query_tags" .== _smQueryTags
|
||||
<*> optionalFieldWithOmittedDefault' "customization" emptySourceCustomization .== _smCustomization
|
||||
@ -485,6 +553,7 @@ mkSourceMetadata name backendSourceKind config customization healthCheckConfig =
|
||||
backendSourceKind
|
||||
mempty
|
||||
mempty
|
||||
mempty
|
||||
config
|
||||
Nothing
|
||||
customization
|
||||
|
@ -74,6 +74,7 @@ data SourceMetadataObjId b
|
||||
| SMOFunction (FunctionName b)
|
||||
| SMOFunctionPermission (FunctionName b) RoleName
|
||||
| SMOTableObj (TableName b) TableMetadataObjId
|
||||
| SMOCustomSQL G.Name
|
||||
deriving (Generic)
|
||||
|
||||
deriving instance (Backend b) => Show (SourceMetadataObjId b)
|
||||
@ -134,6 +135,7 @@ moiTypeName = \case
|
||||
handleSourceObj = \case
|
||||
SMOTable _ -> "table"
|
||||
SMOFunction _ -> "function"
|
||||
SMOCustomSQL _ -> "custom_sql"
|
||||
SMOFunctionPermission _ _ -> "function_permission"
|
||||
SMOTableObj _ tableObjectId -> case tableObjectId of
|
||||
MTORel _ relType -> relTypeToTxt relType <> "_relation"
|
||||
@ -185,6 +187,7 @@ moiName objectId =
|
||||
<> toTxt functionName
|
||||
<> " in source "
|
||||
<> toTxt source
|
||||
SMOCustomSQL name -> toTxt name <> " in source " <> toTxt source
|
||||
SMOTableObj tableName tableObjectId ->
|
||||
let tableObjectName = case tableObjectId of
|
||||
MTORel name _ -> toTxt name
|
||||
|
@ -60,6 +60,7 @@ import Hasura.RQL.Types.Metadata.Common
|
||||
BackendSourceMetadata (..),
|
||||
ComputedFieldMetadata (..),
|
||||
CronTriggers,
|
||||
CustomSQLMetadata (..),
|
||||
Endpoints,
|
||||
FunctionMetadata (..),
|
||||
InheritedRoles,
|
||||
@ -114,11 +115,12 @@ sourcesToOrdJSONList sources =
|
||||
where
|
||||
sourceMetaToOrdJSON :: BackendSourceMetadata -> AO.Value
|
||||
sourceMetaToOrdJSON (BackendSourceMetadata exists) =
|
||||
AB.dispatchAnyBackend @Backend exists $ \(SourceMetadata {..} :: SourceMetadata b) ->
|
||||
AB.dispatchAnyBackend @Backend exists $ \(SourceMetadata _smName _smKind _smTables _smFunctions _smCustomSQL _smConfiguration _smQueryTags _smCustomization _smHealthCheckConfig :: SourceMetadata b) ->
|
||||
let sourceNamePair = ("name", AO.toOrdered _smName)
|
||||
sourceKindPair = ("kind", AO.toOrdered _smKind)
|
||||
tablesPair = ("tables", AO.array $ map tableMetaToOrdJSON $ sortOn _tmTable $ OM.elems _smTables)
|
||||
functionsPair = listToMaybeOrdPairSort "functions" functionMetadataToOrdJSON _fmFunction _smFunctions
|
||||
customSQLPair = listToMaybeOrdPairSort "custom_sql" customSQLMetaToOrdJSON _csmRootFieldName _smCustomSQL
|
||||
configurationPair = [("configuration", AO.toOrdered _smConfiguration)]
|
||||
queryTagsConfigPair = maybe [] (\queryTagsConfig -> [("query_tags", AO.toOrdered queryTagsConfig)]) _smQueryTags
|
||||
|
||||
@ -129,11 +131,15 @@ sourcesToOrdJSONList sources =
|
||||
in AO.object $
|
||||
[sourceNamePair, sourceKindPair, tablesPair]
|
||||
<> maybeToList functionsPair
|
||||
<> maybeToList customSQLPair
|
||||
<> configurationPair
|
||||
<> queryTagsConfigPair
|
||||
<> customizationPair
|
||||
<> healthCheckPair
|
||||
|
||||
customSQLMetaToOrdJSON :: (Backend b) => CustomSQLMetadata b -> AO.Value
|
||||
customSQLMetaToOrdJSON = AO.toOrdered . toJSON
|
||||
|
||||
tableMetaToOrdJSON :: (Backend b) => TableMetadata b -> AO.Value
|
||||
tableMetaToOrdJSON
|
||||
( TableMetadata
|
||||
|
@ -12,6 +12,7 @@ module Hasura.RQL.Types.Source
|
||||
unsafeSourceName,
|
||||
unsafeSourceTables,
|
||||
siConfiguration,
|
||||
siCustomSQL,
|
||||
siFunctions,
|
||||
siName,
|
||||
siQueryTagsConfig,
|
||||
@ -50,6 +51,7 @@ import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.Function
|
||||
import Hasura.RQL.Types.HealthCheck
|
||||
import Hasura.RQL.Types.Instances ()
|
||||
import Hasura.RQL.Types.Metadata.Common (CustomSQLFields)
|
||||
import Hasura.RQL.Types.QueryTags
|
||||
import Hasura.RQL.Types.SourceCustomization
|
||||
import Hasura.RQL.Types.Table
|
||||
@ -66,6 +68,7 @@ data SourceInfo b = SourceInfo
|
||||
{ _siName :: SourceName,
|
||||
_siTables :: TableCache b,
|
||||
_siFunctions :: FunctionCache b,
|
||||
_siCustomSQL :: CustomSQLFields b,
|
||||
_siConfiguration :: ~(SourceConfig b),
|
||||
_siQueryTagsConfig :: Maybe QueryTagsConfig,
|
||||
_siCustomization :: ResolvedSourceCustomization
|
||||
@ -108,7 +111,7 @@ unsafeSourceInfo = AB.unpackAnyBackend
|
||||
unsafeSourceName :: BackendSourceInfo -> SourceName
|
||||
unsafeSourceName bsi = AB.dispatchAnyBackend @Backend bsi go
|
||||
where
|
||||
go (SourceInfo name _ _ _ _ _) = name
|
||||
go (SourceInfo name _ _ _ _ _ _) = name
|
||||
|
||||
unsafeSourceTables :: forall b. HasTag b => BackendSourceInfo -> Maybe (TableCache b)
|
||||
unsafeSourceTables = fmap _siTables . unsafeSourceInfo @b
|
||||
|
@ -21,6 +21,7 @@ module Hasura.Server.API.Backend
|
||||
tableCommands,
|
||||
tablePermissionsCommands,
|
||||
computedFieldCommands,
|
||||
nativeAccessCommands,
|
||||
)
|
||||
where
|
||||
|
||||
@ -159,3 +160,10 @@ computedFieldCommands =
|
||||
[ commandParser "add_computed_field" $ RMAddComputedField . mkAnyBackend @b,
|
||||
commandParser "drop_computed_field" $ RMDropComputedField . mkAnyBackend @b
|
||||
]
|
||||
|
||||
nativeAccessCommands :: forall (b :: BackendType). Backend b => [CommandParser b]
|
||||
nativeAccessCommands =
|
||||
[ commandParser "get_custom_sql" $ RMGetCustomSQL . mkAnyBackend @b,
|
||||
commandParser "track_custom_sql" $ RMTrackCustomSQL . mkAnyBackend @b,
|
||||
commandParser "untrack_custom_sql" $ RMUntrackCustomSQL . mkAnyBackend @b
|
||||
]
|
||||
|
@ -26,6 +26,7 @@ import Hasura.Prelude hiding (first)
|
||||
import Hasura.RQL.DDL.Action
|
||||
import Hasura.RQL.DDL.ApiLimit
|
||||
import Hasura.RQL.DDL.ComputedField
|
||||
import Hasura.RQL.DDL.CustomSQL qualified as CustomSQL
|
||||
import Hasura.RQL.DDL.CustomTypes
|
||||
import Hasura.RQL.DDL.DataConnector
|
||||
import Hasura.RQL.DDL.Endpoint
|
||||
@ -127,6 +128,10 @@ data RQLMetadataV1
|
||||
| -- Computed fields
|
||||
RMAddComputedField !(AnyBackend AddComputedField)
|
||||
| RMDropComputedField !(AnyBackend DropComputedField)
|
||||
| -- Native access
|
||||
RMGetCustomSQL !(AnyBackend CustomSQL.GetCustomSQL)
|
||||
| RMTrackCustomSQL !(AnyBackend CustomSQL.TrackCustomSQL)
|
||||
| RMUntrackCustomSQL !(AnyBackend CustomSQL.UntrackCustomSQL)
|
||||
| -- Tables event triggers
|
||||
RMCreateEventTrigger !(AnyBackend (Unvalidated1 CreateEventTriggerQuery))
|
||||
| RMDeleteEventTrigger !(AnyBackend DeleteEventTriggerQuery)
|
||||
@ -478,6 +483,9 @@ queryModifiesMetadata = \case
|
||||
RMGetSourceTables _ -> False
|
||||
RMGetTableInfo _ -> False
|
||||
RMSuggestRelationships _ -> False
|
||||
RMGetCustomSQL _ -> False
|
||||
RMTrackCustomSQL _ -> True
|
||||
RMUntrackCustomSQL _ -> True
|
||||
RMBulk qs -> any queryModifiesMetadata qs
|
||||
-- We used to assume that the fallthrough was True,
|
||||
-- but it is better to be explicit here to warn when new constructors are added.
|
||||
@ -656,6 +664,9 @@ runMetadataQueryV1M env currentResourceVersion = \case
|
||||
RMDropFunctionPermission q -> dispatchMetadata runDropFunctionPermission q
|
||||
RMAddComputedField q -> dispatchMetadata runAddComputedField q
|
||||
RMDropComputedField q -> dispatchMetadata runDropComputedField q
|
||||
RMGetCustomSQL q -> dispatchMetadata CustomSQL.runGetCustomSQL q
|
||||
RMTrackCustomSQL q -> dispatchMetadata CustomSQL.runTrackCustomSQL q
|
||||
RMUntrackCustomSQL q -> dispatchMetadata CustomSQL.runUntrackCustomSQL q
|
||||
RMCreateEventTrigger q ->
|
||||
dispatchMetadataAndEventTrigger
|
||||
( validateTransforms
|
||||
|
@ -5,6 +5,7 @@ where
|
||||
|
||||
import Hasura.RQL.DDL.Action
|
||||
import Hasura.RQL.DDL.ComputedField
|
||||
import Hasura.RQL.DDL.CustomSQL qualified as CustomSQL
|
||||
import Hasura.RQL.DDL.DataConnector
|
||||
import Hasura.RQL.DDL.EventTrigger
|
||||
import Hasura.RQL.DDL.Metadata
|
||||
@ -82,6 +83,10 @@ data RQLMetadataV1
|
||||
| -- Computed fields
|
||||
RMAddComputedField !(AnyBackend AddComputedField)
|
||||
| RMDropComputedField !(AnyBackend DropComputedField)
|
||||
| -- Native access
|
||||
RMGetCustomSQL !(AnyBackend CustomSQL.GetCustomSQL)
|
||||
| RMTrackCustomSQL !(AnyBackend CustomSQL.TrackCustomSQL)
|
||||
| RMUntrackCustomSQL !(AnyBackend CustomSQL.UntrackCustomSQL)
|
||||
| -- Tables event triggers
|
||||
RMCreateEventTrigger !(AnyBackend (Unvalidated1 CreateEventTriggerQuery))
|
||||
| RMDeleteEventTrigger !(AnyBackend DeleteEventTriggerQuery)
|
||||
|
@ -161,6 +161,7 @@ migrateCatalog maybeDefaultSourceConfig extensionsSchema maintenanceMode migrati
|
||||
PostgresVanillaKind
|
||||
mempty
|
||||
mempty
|
||||
mempty
|
||||
defaultSourceConfig
|
||||
Nothing
|
||||
emptySourceCustomization
|
||||
@ -334,7 +335,7 @@ migrations maybeDefaultSourceConfig dryRun maintenanceMode =
|
||||
defaultSourceMetadata =
|
||||
BackendSourceMetadata $
|
||||
AB.mkAnyBackend $
|
||||
SourceMetadata defaultSource PostgresVanillaKind _mnsTables _mnsFunctions defaultSourceConfig Nothing emptySourceCustomization Nothing
|
||||
SourceMetadata defaultSource PostgresVanillaKind _mnsTables _mnsFunctions mempty defaultSourceConfig Nothing emptySourceCustomization Nothing
|
||||
in Metadata
|
||||
(OMap.singleton defaultSource defaultSourceMetadata)
|
||||
_mnsRemoteSchemas
|
||||
|
@ -320,6 +320,7 @@ spec = do
|
||||
{ _siName = SNDefault,
|
||||
_siTables = makeTableCache [albumTableInfo, trackTableInfo],
|
||||
_siFunctions = mempty,
|
||||
_siCustomSQL = mempty,
|
||||
_siConfiguration = notImplementedYet "SourceConfig",
|
||||
_siQueryTagsConfig = Nothing,
|
||||
_siCustomization = ResolvedSourceCustomization mempty mempty HasuraCase Nothing
|
||||
|
@ -99,6 +99,7 @@ runUpdateFieldTest UpdateTestSetup {..} =
|
||||
{ _siName = SNDefault,
|
||||
_siTables = HM.singleton table tableInfo,
|
||||
_siFunctions = mempty,
|
||||
_siCustomSQL = mempty,
|
||||
_siConfiguration = notImplementedYet "SourceConfig",
|
||||
_siQueryTagsConfig = Nothing,
|
||||
_siCustomization = ResolvedSourceCustomization mempty mempty HasuraCase Nothing
|
||||
|
Loading…
Reference in New Issue
Block a user