feat(SqlServer): Configuration option to disable the connection pool

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9834
Co-authored-by: Samir Talwar <47582+SamirTalwar@users.noreply.github.com>
GitOrigin-RevId: 6b7f8bd660ddda0cae3a5457267f5af4906ff65a
This commit is contained in:
Philip Lykke Carlsen 2023-07-18 20:39:20 +02:00 committed by hasura-bot
parent 58ce99e63a
commit 462abadb8c
7 changed files with 159 additions and 40 deletions

View File

@ -191,12 +191,22 @@ and the two generally shouldn't be enabled at the same time.
## MsSQLPoolSettings {#mssqlpoolsettings}
Pool settings have two disjoint cases.
This schema indicates that the source uses a connection pool (the default):
| Key | Required | Schema | Description |
| --------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| max_connections | false | `Integer` | Maximum number of connections to be kept in the pool (default: 50) |
| total_max_connections | false | `Integer` | Maximum number of total connections across any number of Hasura Cloud instances (default: 50). Takes precedence over `max_connections` in Cloud projects. _(Only available in Cloud)_ |
| idle_timeout | false | `Integer` | The idle timeout (in seconds) per connection (default: 180) |
This schema indicates that the source does not use a connection pool:
| Key | Required | Schema | Description |
| -------------- | -------- | --------- | ------------------------------------------------------ |
| enable | true | `Bool` | Set to `false` to disable the connection pool entirely |
## PGColumnType {#pgcolumntype}
<div className="parsed-literal">

View File

@ -4977,7 +4977,20 @@
"type": "string"
},
"pool_settings": {
"$ref": "#/components/schemas/MSSQLPoolSettings"
"additionalProperties": true,
"anyOf": [
{
"enum": [
{
"enable": false
}
],
"type": "object"
},
{
"$ref": "#/components/schemas/MSSQLPoolSettings"
}
]
}
},
"required": [

View File

@ -142,6 +142,7 @@ library
Test.Databases.Postgres.JsonbSpec
Test.Databases.Postgres.TimestampSpec
Test.Databases.Postgres.UniqueConstraintsSpec
Test.Databases.SQLServer.ConnectionPoolSpec
Test.Databases.SQLServer.DefaultValues.OnConflictSpec
Test.Databases.SQLServer.InsertVarcharColumnSpec
Test.Databases.SQLServer.VarcharLiteralsSpec

View File

@ -0,0 +1,67 @@
{-# LANGUAGE QuasiQuotes #-}
-- | Tests that the connection pool can be configured in different ways for SqlServer.
module Test.Databases.SQLServer.ConnectionPoolSpec (spec) where
import Data.Aeson (Value)
import Data.List.NonEmpty qualified as NE
import Harness.Backend.Sqlserver qualified as Sqlserver
import Harness.Constants qualified as Constants
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Yaml (yaml)
import Harness.Test.Fixture qualified as Fixture
import Harness.TestEnvironment
import Harness.Yaml
import Hasura.Prelude
import Test.Hspec
spec :: SpecWith GlobalTestEnvironment
spec =
Fixture.run
( NE.fromList
[ (Fixture.fixture $ Fixture.Backend Sqlserver.backendTypeMetadata)
]
)
tests
--------------------------------------------------------------------------------
-- Tests
tests :: SpecWith TestEnvironment
tests = do
describe "Connection pool" do
it "Can be disabled" \testEnvironment -> do
GraphqlEngine.setSource testEnvironment (sourceMetadataNoPool testEnvironment) Nothing
actual <- GraphqlEngine.exportMetadata testEnvironment
actual
`shouldAtLeastBe` [yaml|
sources:
- configuration:
connection_info:
pool_settings:
enable: false
|]
sourceMetadataNoPool :: TestEnvironment -> Value
sourceMetadataNoPool TestEnvironment {uniqueTestId} =
let source = Fixture.backendSourceName Sqlserver.backendTypeMetadata
backendType = Fixture.backendTypeString Sqlserver.backendTypeMetadata
sourceConfiguration = defaultSourceConfiguration
in [yaml|
name: *source
kind: *backendType
tables: []
configuration: *sourceConfiguration
|]
where
defaultSourceConfiguration :: Value
defaultSourceConfiguration =
[yaml|
connection_info:
database_url: *sqlserverConnectInfo
pool_settings:
enable: false
|]
sqlserverConnectInfo = Constants.sqlserverConnectInfo uniqueTestId

View File

@ -30,21 +30,27 @@ newtype ConnectionString = ConnectionString {unConnectionString :: Text}
instance HasCodec ConnectionString where
codec = dimapCodec ConnectionString unConnectionString codec
data ConnectionOptions = ConnectionOptions
{ _coConnections :: Int,
_coStripes :: Int,
_coIdleTime :: Int
}
data ConnectionOptions
= ConnectionOptions
{ _coConnections :: Int,
_coStripes :: Int,
_coIdleTime :: Int
}
| ConnectionOptionsNoPool
deriving (Show, Eq)
-- | ODBC connection pool
newtype MSSQLPool = MSSQLPool (Pool.Pool ODBC.Connection)
data MSSQLPool
= MSSQLPool (Pool.Pool ODBC.Connection)
| MSSQLNoPool (IO ODBC.Connection)
-- | Initialize an MSSQL pool with given connection configuration
initMSSQLPool ::
ConnectionString ->
ConnectionOptions ->
IO MSSQLPool
initMSSQLPool (ConnectionString connString) ConnectionOptionsNoPool = do
return $ MSSQLNoPool (ODBC.connect connString)
initMSSQLPool (ConnectionString connString) ConnectionOptions {..} = do
MSSQLPool
<$> Pool.createPool
@ -58,6 +64,7 @@ initMSSQLPool (ConnectionString connString) ConnectionOptions {..} = do
drainMSSQLPool :: MSSQLPool -> IO ()
drainMSSQLPool (MSSQLPool pool) =
Pool.destroyAllResources pool
drainMSSQLPool MSSQLNoPool {} = return ()
withMSSQLPool ::
(MonadBaseControl IO m) =>
@ -66,6 +73,8 @@ withMSSQLPool ::
m (Either ODBC.ODBCException a)
withMSSQLPool (MSSQLPool pool) action = do
try $ Pool.withResource pool action
withMSSQLPool (MSSQLNoPool connect) action = do
try $ bracket (liftBaseWith (const connect)) (\conn -> liftBaseWith (const (ODBC.close conn))) action
-- | Resize a pool
resizePool :: MSSQLPool -> Int -> IO ()
@ -74,6 +83,8 @@ resizePool (MSSQLPool pool) resizeTo = do
Pool.resizePool pool resizeTo
-- Trim pool by destroying excess resources, if any
Pool.tryTrimPool pool
resizePool (MSSQLNoPool {}) _ = return ()
getInUseConnections :: MSSQLPool -> IO Int
getInUseConnections (MSSQLPool pool) = Pool.getInUseResourceCount $ pool
getInUseConnections MSSQLNoPool {} = return 0

View File

@ -1465,13 +1465,15 @@ mkPgSourceResolver pgLogger env _ config = runExceptT do
mkMSSQLSourceResolver :: SourceResolver ('MSSQL)
mkMSSQLSourceResolver env _name (MSSQLConnConfiguration connInfo _) = runExceptT do
let MSSQLConnectionInfo iConnString MSSQLPoolSettings {..} isolationLevel = connInfo
connOptions =
MSPool.ConnectionOptions
{ _coConnections = fromMaybe defaultMSSQLMaxConnections _mpsMaxConnections,
_coStripes = 1,
_coIdleTime = _mpsIdleTimeout
}
let MSSQLConnectionInfo iConnString poolSettings isolationLevel = connInfo
connOptions = case poolSettings of
MSSQLPoolSettings {..} ->
MSPool.ConnectionOptions
{ _coConnections = fromMaybe defaultMSSQLMaxConnections _mpsMaxConnections,
_coStripes = 1,
_coIdleTime = _mpsIdleTimeout
}
MSSQLPoolSettingsNoPool -> MSPool.ConnectionOptionsNoPool
(connString, mssqlPool) <- createMSSQLPool iConnString connOptions env
let mssqlExecCtx = mkMSSQLExecCtx isolationLevel mssqlPool NeverResizePool
numReadReplicas = 0

View File

@ -99,42 +99,57 @@ instance FromJSON InputConnectionString where
s@(String _) -> RawString <$> parseJSON s
_ -> fail "one of string or object must be provided"
data MSSQLPoolSettings = MSSQLPoolSettings
{ _mpsMaxConnections :: Maybe Int,
_mpsTotalMaxConnections :: Maybe Int,
_mpsIdleTimeout :: Int
}
data MSSQLPoolSettings
= MSSQLPoolSettings
{ _mpsMaxConnections :: Maybe Int,
_mpsTotalMaxConnections :: Maybe Int,
_mpsIdleTimeout :: Int
}
| MSSQLPoolSettingsNoPool
deriving (Show, Eq, Generic)
instance Hashable MSSQLPoolSettings
instance NFData MSSQLPoolSettings
instance ToJSON MSSQLPoolSettings where
toJSON = genericToJSON hasuraJSON
toEncoding = genericToEncoding hasuraJSON
deriving via AC.Autodocodec MSSQLPoolSettings instance ToJSON MSSQLPoolSettings
instance FromJSON MSSQLPoolSettings where
parseJSON = withObject "MSSQL pool settings" $ \o ->
MSSQLPoolSettings
<$> o
.:? "max_connections"
<*> o
.:? "total_max_connections"
<*> o
.:? "idle_timeout"
.!= _mpsIdleTimeout defaultMSSQLPoolSettings
deriving via AC.Autodocodec MSSQLPoolSettings instance FromJSON MSSQLPoolSettings
instance HasCodec MSSQLPoolSettings where
codec =
AC.object "MSSQLPoolSettings"
$ MSSQLPoolSettings
<$> optionalFieldWithDefault' "max_connections" (Just defaultMSSQLMaxConnections)
AC..= _mpsMaxConnections
<*> optionalFieldOrNull' "total_max_connections"
AC..= _mpsTotalMaxConnections
<*> optionalFieldWithDefault' "idle_timeout" (_mpsIdleTimeout defaultMSSQLPoolSettings)
AC..= _mpsIdleTimeout
AC.matchChoiceCodec codecNoPool codecWithPool toInput
where
toInput :: MSSQLPoolSettings -> Either MSSQLPoolSettings MSSQLPoolSettings
toInput = \case
p@MSSQLPoolSettingsNoPool {} -> Left p
p@MSSQLPoolSettings {} -> Right p
codecNoPool :: AC.JSONCodec MSSQLPoolSettings
codecNoPool =
AC.bimapCodec
( \case
False -> Right MSSQLPoolSettingsNoPool
True -> Left "impossible, guarded by 'EqCodec False"
)
( \case
MSSQLPoolSettingsNoPool -> False
_ -> True
)
$ AC.EqCodec False
$ AC.object "MSSQLPoolSettingsNoPool"
$ AC.requiredField "enable" "Whether the connection pool is entirely disabled"
codecWithPool :: AC.JSONCodec MSSQLPoolSettings
codecWithPool =
AC.object "MSSQLPoolSettings"
$ MSSQLPoolSettings
<$> optionalFieldWithDefault' "max_connections" (Just defaultMSSQLMaxConnections)
AC..= _mpsMaxConnections
<*> optionalFieldOrNull' "total_max_connections"
AC..= _mpsTotalMaxConnections
<*> optionalFieldWithDefault' "idle_timeout" (_mpsIdleTimeout defaultMSSQLPoolSettings)
AC..= _mpsIdleTimeout
defaultMSSQLMaxConnections :: Int
defaultMSSQLMaxConnections = 50