server/mssql: integrate insert mutation schema parser for MSSQL backend

>

### Description
>
This PR is an incremental work towards [enabling insert mutations on MSSQL](https://github.com/hasura/graphql-engine-mono/pull/1974). In this PR, we generate insert mutation schema parser for MSSQL backend.

### Changelog

- [ ] `CHANGELOG.md` is updated with user-facing content relevant to this PR. If no changelog is required, then add the `no-changelog-required` label.

### Affected components

- [x] Server

https://github.com/hasura/graphql-engine-mono/pull/2141

GitOrigin-RevId: 8595008dece35f7fded9c52e134de8b97b64f53f
This commit is contained in:
Rakesh Emmadi 2021-08-31 19:04:43 +05:30 committed by hasura-bot
parent b50df8a24b
commit 7ca48decfb
25 changed files with 104 additions and 60 deletions

View File

@ -96,6 +96,9 @@ resolveSource sourceConfig =
case mode of
Nullable -> True
_ -> False
, prciIsIdentity = notIdentityColumn -- TODO: Determine the identity-ness of BigQuery columns. Until then we assume them as not identity.
-- For more details refer https://en.wikipedia.org/wiki/Identity_column.
-- issue: https://github.com/hasura/graphql-engine/issues/7450
, prciDescription = Nothing
}
| (position, RestFieldSchema {name, type', mode}) <-

View File

@ -45,8 +45,9 @@ instance BackendSchema 'BigQuery where
buildFunctionMutationFields = bqBuildFunctionMutationFields
-- backend extensions
relayExtension = Nothing
nodesAggExtension = Just ()
relayExtension = Nothing
nodesAggExtension = Just ()
nestedInsertsExtension = Nothing
-- table arguments
tableArguments = bqTableArgs

View File

@ -39,6 +39,7 @@ instance Backend 'BigQuery where
type XComputedField 'BigQuery = XDisable
type XRelay 'BigQuery = XDisable
type XNodesAgg 'BigQuery = XEnable
type XNestedInserts 'BigQuery = XDisable
type ExtraTableMetadata 'BigQuery = ()

View File

@ -212,7 +212,7 @@ msDBMutationPlan
-> QueryTagsComment
-> m (DBStepInfo 'MSSQL)
msDBMutationPlan _userInfo _stringifyNum _sourceName _sourceConfig _mrf _queryTags =
throw500 "mutations are not supported in MSSQL; this should be unreachable"
throw400 NotSupported "mutations are not supported in MSSQL"
-- subscription

View File

@ -37,7 +37,7 @@ instance BackendSchema 'MSSQL where
-- top level parsers
buildTableQueryFields = GSB.buildTableQueryFields
buildTableRelayQueryFields = msBuildTableRelayQueryFields
buildTableInsertMutationFields = msBuildTableInsertMutationFields
buildTableInsertMutationFields = GSB.buildTableInsertMutationFields
buildTableUpdateMutationFields = msBuildTableUpdateMutationFields
buildTableDeleteMutationFields = msBuildTableDeleteMutationFields
buildFunctionQueryFields = msBuildFunctionQueryFields
@ -45,8 +45,9 @@ instance BackendSchema 'MSSQL where
buildFunctionMutationFields = msBuildFunctionMutationFields
-- backend extensions
relayExtension = Nothing
nodesAggExtension = Just ()
relayExtension = Nothing
nodesAggExtension = Just ()
nestedInsertsExtension = Nothing
-- table arguments
tableArguments = msTableArgs
@ -82,20 +83,6 @@ msBuildTableRelayQueryFields
msBuildTableRelayQueryFields _sourceName _sourceInfo _tableName _tableInfo _gqlName _pkeyColumns _selPerms =
pure []
msBuildTableInsertMutationFields
:: MonadBuildSchema 'MSSQL r m n
=> SourceName
-> SourceConfig 'MSSQL
-> TableName 'MSSQL
-> TableInfo 'MSSQL
-> G.Name
-> InsPermInfo 'MSSQL
-> Maybe (SelPermInfo 'MSSQL)
-> Maybe (UpdPermInfo 'MSSQL)
-> m [FieldParser n (MutationRootField UnpreparedValue)]
msBuildTableInsertMutationFields _sourceName _sourceInfo _tableName _tableInfo _gqlName _insPerms _selPerms _updPerms =
pure []
msBuildTableUpdateMutationFields
:: MonadBuildSchema 'MSSQL r m n
=> SourceName

View File

@ -42,6 +42,7 @@ instance Backend 'MSSQL where
type XComputedField 'MSSQL = XDisable
type XRelay 'MSSQL = XDisable
type XNodesAgg 'MSSQL = XEnable
type XNestedInserts 'MSSQL = XDisable
functionArgScalarType :: FunctionArgType 'MSSQL -> ScalarType 'MSSQL
functionArgScalarType = absurd

View File

@ -70,6 +70,7 @@ data SysColumn = SysColumn
, scColumnId :: Int
, scUserTypeId :: Int
, scIsNullable :: Bool
, scIsIdentity :: IsIdentityColumn
, scJoinedSysType :: SysType
, scJoinedForeignKeyColumns :: [SysForeignKeyColumn]
} deriving (Show, Generic)
@ -135,6 +136,7 @@ transformColumn columnInfo =
-- or dropped. We assume here that this arbitrary column id can
-- serve the same purpose.
prciIsNullable = scIsNullable columnInfo
prciIsIdentity = scIsIdentity columnInfo
prciDescription = Nothing
prciType = parseScalarType $ styName $ scJoinedSysType columnInfo
foreignKeys = scJoinedForeignKeyColumns columnInfo <&> \foreignKeyColumn ->

View File

@ -46,6 +46,7 @@ instance BackendSchema 'MySQL where
computedField = error "computedField: MySQL backend does not support this operation yet."
node = error "node: MySQL backend does not support this operation yet."
columnDefaultValue = error "columnDefaultValue: MySQL backend does not support this operation yet."
nestedInsertsExtension = Nothing
mysqlTableArgs
:: forall r m n

View File

@ -38,6 +38,7 @@ instance Backend 'MySQL where
type XRelay 'MySQL = Void
type XNodesAgg 'MySQL = XEnable
type ExtraTableMetadata 'MySQL = ()
type XNestedInserts 'MySQL = XDisable
functionArgScalarType :: FunctionArgType 'MySQL -> ScalarType 'MySQL
functionArgScalarType = error "functionArgScalarType: not implemented yet"

View File

@ -46,6 +46,9 @@ mergeMetadata InformationSchema{..} =
, prciPosition = fromIntegral isOrdinalPosition
, prciType = parseMySQLScalarType isColumnType -- TODO: This needs to become more precise by considering Field length and character-set
, prciIsNullable = isIsNullable == "YES" -- ref: https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html
, prciIsIdentity = notIdentityColumn -- TODO: The identity-ness of column is not explicitly available in MySQL INFORMATION_SCHEMA.COLUMNS table.
-- Determine this value from 'EXTRA' field. Till then we assume it as not identity.
-- issue: https://github.com/hasura/graphql-engine/issues/7450
, prciDescription = Just $ G.Description isColumnComment
}
]

View File

@ -80,14 +80,14 @@ insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput plan
bool withoutRelsInsert withRelsInsert anyRelsToInsert
where
IR.AnnIns insObjs table conflictClause checkCondition columnInfos defVals = multiObjIns
allInsObjRels = concatMap IR._aioObjRels insObjs
allInsArrRels = concatMap IR._aioArrRels insObjs
allInsObjRels = concatMap IR.getInsertObjectRelationships insObjs
allInsArrRels = concatMap IR.getInsertArrayRelationships insObjs
anyRelsToInsert = not $ null allInsArrRels && null allInsObjRels
withoutRelsInsert = do
indexedForM_ (IR._aioColumns <$> insObjs) \column ->
indexedForM_ (IR.getInsertColumns <$> insObjs) \column ->
validateInsert (map fst column) [] (map fst additionalColumns)
let columnValues = map (mkSQLRow defVals) $ union additionalColumns . IR._aioColumns <$> insObjs
let columnValues = map (mkSQLRow defVals) $ union additionalColumns . IR.getInsertColumns <$> insObjs
columnNames = Map.keys defVals
insertQuery = IR.InsertQueryP1
table
@ -151,7 +151,9 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tra
return (totAffRows, colValM)
where
IR.AnnIns (IR.Single annObj) table onConflict checkCond allColumns defaultValues = singleObjIns
IR.AnnInsObj columns objectRels arrayRels = annObj
columns = IR.getInsertColumns annObj
objectRels = IR.getInsertObjectRelationships annObj
arrayRels = IR.getInsertArrayRelationships annObj
afterInsert, beforeInsert :: [IR.ObjRelIns ('Postgres pgKind) PG.SQLExp]
(afterInsert, beforeInsert) =

View File

@ -129,8 +129,9 @@ instance
tableArguments = defaultTableArgs
-- backend extensions
relayExtension = pgkRelayExtension @pgKind
nodesAggExtension = Just ()
relayExtension = pgkRelayExtension @pgKind
nodesAggExtension = Just ()
nestedInsertsExtension = Just ()
-- indivdual components
columnParser = columnParser

View File

@ -77,6 +77,7 @@ instance
type XComputedField ('Postgres pgKind) = XEnable
type XRelay ('Postgres pgKind) = XEnable
type XNodesAgg ('Postgres pgKind) = XEnable
type XNestedInserts ('Postgres pgKind) = XEnable
functionArgScalarType = PG.mkFunctionArgScalarType
isComparableType = PG.isComparableType

View File

@ -277,11 +277,11 @@ resolveAsyncActionQuery userInfo annAction =
RS.mkAnnColumnField (mkPGColumnInfo columnInfoArgs) Nothing Nothing
mkPGColumnInfo (column', columnType) =
ColumnInfo column' (G.unsafeMkName $ getPGColTxt column') 0 (ColumnScalar columnType) True Nothing
ColumnInfo column' (G.unsafeMkName $ getPGColTxt column') 0 (ColumnScalar columnType) True notIdentityColumn Nothing
tableBoolExpression =
let actionIdColumnInfo = ColumnInfo (unsafePGCol "id") $$(G.litName "id")
0 (ColumnScalar PGUUID) False Nothing
0 (ColumnScalar PGUUID) False notIdentityColumn Nothing
actionIdColumnEq = BoolFld $ AVColumn actionIdColumnInfo [AEQ True $ UVLiteral $ S.SELit $ actionIdToText actionId]
sessionVarsColumnInfo = mkPGColumnInfo sessionVarsColumn
sessionVarValue = UVParameter Nothing $ ColumnValue (ColumnScalar PGJSONB) $

View File

@ -201,7 +201,7 @@ actionOutputFields outputType annotatedObject = do
AOFTScalar def -> customScalarParser def
AOFTEnum def -> customEnumParser def
pgColumnInfo =
ColumnInfo (unsafePGCol $ G.unName fieldName) fieldName 0 (ColumnScalar PGJSON) (G.isNullable gType) Nothing
ColumnInfo (unsafePGCol $ G.unName fieldName) fieldName 0 (ColumnScalar PGJSON) (G.isNullable gType) notIdentityColumn Nothing
in P.wrapFieldParser gType selection $> RQL.mkAnnColumnField pgColumnInfo Nothing Nothing
relationshipFieldParser

View File

@ -45,7 +45,7 @@ import qualified Hasura.RQL.IR.Select as IR
import qualified Hasura.RQL.IR.Update as IR
import Hasura.Base.Error
import Hasura.GraphQL.Parser
import Hasura.GraphQL.Parser hiding (Type)
import Hasura.GraphQL.Schema.Common
import Hasura.RQL.IR
import Hasura.RQL.Types hiding (EnumValueInfo)
@ -154,6 +154,7 @@ class Backend b => BackendSchema (b :: BackendType) where
-- backend extensions
relayExtension :: Maybe (XRelay b)
nodesAggExtension :: Maybe (XNodesAgg b)
nestedInsertsExtension :: Maybe (XNestedInserts b)
-- individual components
columnParser

View File

@ -111,7 +111,7 @@ tableFieldsInput
=> SourceName
-> TableInfo b -- ^ qualified name of the table
-> InsPermInfo b -- ^ insert permissions of the table
-> m (Parser 'Input n (IR.AnnInsObj b (UnpreparedValue b)))
-> m (Parser 'Input n (IR.AnnotatedInsertRow b (UnpreparedValue b)))
tableFieldsInput sourceName tableInfo insertPerms = memoizeOn 'tableFieldsInput (sourceName, tableName) do
tableGQLName <- getTableGQLName tableInfo
roleName <- askRoleName
@ -120,12 +120,14 @@ tableFieldsInput sourceName tableInfo insertPerms = memoizeOn 'tableFieldsInput
FIComputedField _ -> pure Nothing
FIRemoteRelationship _ -> pure Nothing
FIColumn columnInfo ->
whenMaybe (Set.member (pgiColumn columnInfo) (ipiCols insertPerms)) do
-- Value of an identity column is generated by the database. We cannot a insert into it. So, identity
-- columns are omitted in the insert fields input.
whenMaybe (Set.member (pgiColumn columnInfo) (ipiCols insertPerms) && not (isIdentityColumn $ pgiIsIdentity columnInfo)) do
let columnName = pgiName columnInfo
columnDesc = pgiDescription columnInfo
fieldParser <- columnParser (pgiType columnInfo) (G.Nullability $ pgiIsNullable columnInfo)
pure $ P.fieldOptional columnName columnDesc fieldParser `mapField`
\(mkParameter -> value) -> IR.AnnInsObj [(pgiColumn columnInfo, value)] [] []
\(mkParameter -> value) -> IR.AIColumn (pgiColumn columnInfo, value)
FIRelationship relationshipInfo -> runMaybeT $ do
let otherTableName = riRTable relationshipInfo
relName = riName relationshipInfo
@ -135,20 +137,21 @@ tableFieldsInput sourceName tableInfo insertPerms = memoizeOn 'tableFieldsInput
insPerms <- hoistMaybe $ _permIns permissions
let selPerms = _permSel permissions
updPerms = _permUpd permissions
xNestedInserts <- hoistMaybe (nestedInsertsExtension @b)
lift $ case riType relationshipInfo of
ObjRel -> do
parser <- objectRelationshipInput sourceName otherTableInfo insPerms selPerms updPerms
pure $ P.fieldOptional relFieldName Nothing parser `mapField`
\objRelIns -> IR.AnnInsObj [] [IR.RelIns objRelIns relationshipInfo] []
\objRelIns -> IR.AIObjectRelationship xNestedInserts $ IR.RelIns objRelIns relationshipInfo
ArrRel -> do
parser <- P.nullable <$> arrayRelationshipInput sourceName otherTableInfo insPerms selPerms updPerms
pure $ P.fieldOptional relFieldName Nothing parser <&> \arrRelIns -> do
rel <- join arrRelIns
Just $ IR.AnnInsObj [] [] [IR.RelIns rel relationshipInfo | not $ null $ IR._aiInsObj rel]
IR.AIArrayRelationship xNestedInserts <$>
if not (null $ IR._aiInsObj rel) then Just (IR.RelIns rel relationshipInfo) else Nothing
let objectName = tableGQLName <> $$(G.litName "_insert_input")
objectDesc = G.Description $ "input type for inserting data into table " <>> tableName
pure $ P.object objectName (Just objectDesc) $ catMaybes <$> sequenceA objectFields
<&> mconcat
where
tableName = tableInfoName tableInfo
@ -207,7 +210,7 @@ arrayRelationshipInput sourceName tableInfo insertPerms selectPerms updatePerms
mkInsertObject
:: forall b f
. BackendSchema b
=> f (IR.AnnInsObj b (UnpreparedValue b))
=> f (IR.AnnotatedInsertRow b (UnpreparedValue b))
-> TableName b
-> [ColumnInfo b]
-> Maybe (IR.ConflictClauseP1 b (UnpreparedValue b))
@ -222,7 +225,7 @@ mkInsertObject objects table columns conflictClause insertPerms updatePerms =
, _aiTableCols = columns
, _aiDefVals = defaultValues
}
where insertCheck = (fmap . fmap) partialSQLExpToUnpreparedValue $ ipiCheck insertPerms
where insertCheck = fmap partialSQLExpToUnpreparedValue <$> ipiCheck insertPerms
updateCheck = (fmap . fmap) partialSQLExpToUnpreparedValue <$> (upiCheck =<< updatePerms)
defaultValues = Map.union (partialSQLExpToUnpreparedValue <$> ipiSet insertPerms)
$ Map.fromList [(column, UVLiteral $ columnDefaultValue @b column) | column <- pgiColumn <$> columns]
@ -260,7 +263,7 @@ conflictObject sourceName tableInfo selectPerms updatePerms = runMaybeT $ do
pure $ case columns of
[] -> IR.CP1DoNothing $ Just constraint
_ -> IR.CP1Update constraint columns preSetColumns $
BoolAnd $ catMaybes [whereExp, Just $ (fmap . fmap) partialSQLExpToUnpreparedValue $ upiFilter updatePerms]
BoolAnd $ catMaybes [whereExp, Just (fmap partialSQLExpToUnpreparedValue <$> upiFilter updatePerms)]
pure $ P.object objectName (Just objectDesc) fieldsParser
where preSetColumns = partialSQLExpToUnpreparedValue <$> upiSet updatePerms
@ -363,7 +366,7 @@ mkUpdateObject table columns updatePerms ((opExps, whereExp), mutationOutput) =
, IR.uqp1AllCols = columns
}
where
permissionFilter = (fmap . fmap) partialSQLExpToUnpreparedValue $ upiFilter updatePerms
permissionFilter = fmap partialSQLExpToUnpreparedValue <$> upiFilter updatePerms
checkExp = maybe annBoolExpTrue ((fmap . fmap) partialSQLExpToUnpreparedValue) $ upiCheck updatePerms
@ -424,7 +427,7 @@ mkDeleteObject table columns deletePerms (whereExp, mutationOutput) =
, IR.dqp1AllCols = columns
}
where
permissionFilter = (fmap . fmap) partialSQLExpToUnpreparedValue $ dpiFilter deletePerms
permissionFilter = fmap partialSQLExpToUnpreparedValue <$> dpiFilter deletePerms
-- common

View File

@ -335,8 +335,8 @@ alterColumnsInMetadata
-> TableName b
-> m ()
alterColumnsInMetadata source alteredCols fields sc tn = for_ alteredCols $
\( RawColumnInfo oldName _ oldType _ _
, RawColumnInfo newName _ newType _ _ ) -> do
\( RawColumnInfo{prciName = oldName, prciType = oldType}
, RawColumnInfo{prciName = newName, prciType = newType}) -> do
if | oldName /= newName ->
renameColumnInMetadata oldName newName source tn fields

View File

@ -512,6 +512,7 @@ buildTableCache = Inc.cache proc (source, sourceConfig, dbTablesMeta, tableBuild
, pgiPosition = prciPosition rawInfo
, pgiType = resolvedType
, pgiIsNullable = prciIsNullable rawInfo
, pgiIsIdentity = prciIsIdentity rawInfo
, pgiDescription = prciDescription rawInfo
}
where

View File

@ -16,6 +16,8 @@ module Hasura.RQL.IR.Insert where
import Hasura.Prelude
import Control.Lens ((^?))
import Control.Lens.TH (makePrisms)
import Data.Kind (Type)
import Hasura.RQL.IR.BoolExp
@ -47,7 +49,7 @@ data AnnInsert (b :: BackendType) (r :: BackendType -> Type) v
data AnnIns (b :: BackendType) (f :: Type -> Type) (v :: Type)
= AnnIns
{ _aiInsObj :: !(f (AnnInsObj b v))
{ _aiInsObj :: !(f (AnnotatedInsertRow b v))
, _aiTableName :: !(TableName b)
, _aiConflictClause :: !(Maybe (ConflictClauseP1 b v))
, _aiCheckCond :: !(AnnBoolExp b v, Maybe (AnnBoolExp b v))
@ -67,24 +69,19 @@ newtype Single a = Single { unSingle :: a }
type SingleObjIns b v = AnnIns b Single v
type MultiObjIns b v = AnnIns b [] v
-- | An insert item.
-- The object and array relationships are not unavailable when 'XNestedInserts b = XDisable'
data AnnotatedInsert (b :: BackendType) v
= AIColumn !(Column b, v)
| AIObjectRelationship !(XNestedInserts b) !(ObjRelIns b v)
| AIArrayRelationship !(XNestedInserts b) !(ArrRelIns b v)
deriving (Functor, Foldable, Traversable)
-- | One individual row to be inserted.
-- Contains the columns' values and all the matching recursive relationship inserts.
data AnnInsObj (b :: BackendType) v
= AnnInsObj
{ _aioColumns :: ![(Column b, v)]
, _aioObjRels :: ![ObjRelIns b v]
, _aioArrRels :: ![ArrRelIns b v]
} deriving (Functor, Foldable, Traversable)
instance Semigroup (AnnInsObj backend v) where
(AnnInsObj col1 obj1 rel1) <> (AnnInsObj col2 obj2 rel2) =
AnnInsObj (col1 <> col2) (obj1 <> obj2) (rel1 <> rel2)
instance Monoid (AnnInsObj backend v) where
mempty = AnnInsObj [] [] []
type AnnotatedInsertRow b v = [AnnotatedInsert b v]
-- | One individual relationship.
-- Unlike other types, this one is not parameterized by the type of the leaves
@ -142,3 +139,15 @@ data InsertQueryP1 (b :: BackendType)
, iqp1Output :: !(MutationOutput b)
, iqp1AllCols :: ![ColumnInfo b]
}
-- Template Haskell related
$(makePrisms ''AnnotatedInsert)
getInsertColumns :: AnnotatedInsertRow b v -> [(Column b, v)]
getInsertColumns = mapMaybe (^? _AIColumn)
getInsertObjectRelationships :: AnnotatedInsertRow b v -> [ObjRelIns b v]
getInsertObjectRelationships = mapMaybe (fmap snd . (^? _AIObjectRelationship))
getInsertArrayRelationships :: AnnotatedInsertRow b v -> [ArrRelIns b v]
getInsertArrayRelationships = mapMaybe (fmap snd . (^? _AIArrayRelationship))

View File

@ -120,6 +120,9 @@ class
type XRelay b :: Type
type XNodesAgg b :: Type
-- | Extension to flag the availability of object and array relationships in inserts (aka nested inserts).
type XNestedInserts b :: Type
-- functions on types
functionArgScalarType :: FunctionArgType b -> ScalarType b
isComparableType :: ScalarType b -> Bool

View File

@ -13,6 +13,9 @@ module Hasura.RQL.Types.Column
, ColumnValue(..)
, IsIdentityColumn(..)
, notIdentityColumn
, ColumnInfo(..)
, RawColumnInfo(..)
, PrimaryKeyColumns
@ -145,6 +148,14 @@ parseScalarValuesColumnType
parseScalarValuesColumnType columnType =
indexedMapM (parseScalarValueColumnType columnType)
-- | Identity columns are table fields whose values are generated by the database. Refer https://en.wikipedia.org/wiki/Identity_column.
newtype IsIdentityColumn
= IsIdentityColumn { isIdentityColumn :: Bool}
deriving (Generic, Eq, Show, ToJSON, FromJSON, NFData, Cacheable, Hashable)
notIdentityColumn :: IsIdentityColumn
notIdentityColumn = IsIdentityColumn False
-- | “Raw” column info, as stored in the catalog (but not in the schema cache). Instead of
-- containing a 'PGColumnType', it only contains a 'PGScalarType', which is combined with the
-- 'pcirReferences' field and other table data to eventually resolve the type to a 'PGColumnType'.
@ -157,6 +168,7 @@ data RawColumnInfo (b :: BackendType)
-- consistently identified by its position.
, prciType :: !(ScalarType b)
, prciIsNullable :: !Bool
, prciIsIdentity :: !IsIdentityColumn
, prciDescription :: !(Maybe G.Description)
} deriving (Generic)
deriving instance Backend b => Eq (RawColumnInfo b)
@ -178,6 +190,7 @@ data ColumnInfo (b :: BackendType)
, pgiPosition :: !Int
, pgiType :: !(ColumnType b)
, pgiIsNullable :: !Bool
, pgiIsIdentity :: !IsIdentityColumn
, pgiDescription :: !(Maybe G.Description)
} deriving (Generic)
deriving instance Backend b => Eq (ColumnInfo b)

View File

@ -47,6 +47,7 @@ LEFT JOIN LATERAL
'position', "column".attnum,
'type', coalesce(base_type.typname, "type".typname),
'is_nullable', NOT "column".attnotnull,
'is_identity', coalesce(("info_column".is_identity::boolean), false),
'description', pg_catalog.col_description("table".oid, "column".attnum)
)) AS info
FROM pg_catalog.pg_attribute "column"
@ -54,6 +55,10 @@ LEFT JOIN LATERAL
ON "type".oid = "column".atttypid
LEFT JOIN pg_catalog.pg_type base_type
ON "type".typtype = 'd' AND base_type.oid = "type".typbasetype
LEFT JOIN information_schema.columns "info_column"
ON "info_column".column_name = "column".attname
AND "info_column".table_name = "table".relname
AND "info_column".table_schema = "schema".nspname
WHERE "column".attrelid = "table".oid
-- columns where attnum <= 0 are special, system-defined columns
AND "column".attnum > 0

View File

@ -4,7 +4,7 @@ SELECT ISNULL(
JSON_QUERY([schema].json) AS [joined_sys_schema],
JSON_QUERY([column].json) AS [joined_sys_column]
FROM sys.objects object
CROSS APPLY (SELECT [column].name, [column].column_id, [column].is_nullable, [column].user_type_id,
CROSS APPLY (SELECT [column].name, [column].column_id, [column].is_nullable, [column].is_identity, [column].user_type_id,
JSON_QUERY([types].json) AS [joined_sys_type],
JSON_QUERY(ISNULL([relationships].json,'[]')) AS [joined_foreign_key_columns]
FROM sys.columns [column]

View File

@ -37,6 +37,7 @@ LEFT JOIN LATERAL
'position', "column".attnum,
'type', coalesce(base_type.typname, "type".typname),
'is_nullable', NOT "column".attnotnull,
'is_identity', coalesce(("info_column".is_identity::boolean), false),
'description', pg_catalog.col_description("table".oid, "column".attnum)
)) AS info
FROM pg_catalog.pg_attribute "column"
@ -44,6 +45,10 @@ LEFT JOIN LATERAL
ON "type".oid = "column".atttypid
LEFT JOIN pg_catalog.pg_type base_type
ON "type".typtype = 'd' AND base_type.oid = "type".typbasetype
LEFT JOIN information_schema.columns "info_column"
ON "info_column".column_name = "column".attname
AND "info_column".table_name = "table".relname
AND "info_column".table_schema = "schema".nspname
WHERE "column".attrelid = "table".oid
-- columns where attnum <= 0 are special, system-defined columns
AND "column".attnum > 0