Nested where

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9940
GitOrigin-RevId: c2115f1de4f647c4b559c98183c6260a1f5674dd
This commit is contained in:
David Overton 2023-08-01 15:39:46 +10:00 committed by hasura-bot
parent 374bbd1937
commit 23d40fc500
27 changed files with 233 additions and 99 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@hasura/dc-api-types",
"version": "0.34.0",
"version": "0.35.0",
"description": "Hasura GraphQL Engine Data Connector Agent API types",
"author": "Hasura (https://github.com/hasura/graphql-engine)",
"license": "Apache-2.0",

View File

@ -1759,8 +1759,19 @@
"$ref": "#/components/schemas/ScalarType"
},
"name": {
"description": "The name of the column",
"type": "string"
"additionalProperties": true,
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
],
"description": "The name of the column"
},
"path": {
"default": [],

View File

@ -9,7 +9,7 @@ export type ComparisonColumn = {
/**
* The name of the column
*/
name: string;
name: (string | Array<string>);
/**
* The path to the table that contains the specified column. Missing or empty array means the current table. ["$"] means the query table. No other values are supported at this time.
*/

View File

@ -24,7 +24,7 @@
},
"dc-api-types": {
"name": "@hasura/dc-api-types",
"version": "0.34.0",
"version": "0.35.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
@ -2227,7 +2227,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",
@ -2547,7 +2547,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify": "^4.13.0",
"fastify-metrics": "^9.2.1",
"nanoid": "^3.3.4",
@ -2868,7 +2868,7 @@
"version": "file:reference",
"requires": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"@tsconfig/node16": "^1.0.3",
"@types/node": "^16.11.49",
"@types/xml2js": "^0.4.11",
@ -3080,7 +3080,7 @@
"version": "file:sqlite",
"requires": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"@tsconfig/node16": "^1.0.3",
"@types/node": "^16.11.49",
"@types/sqlite3": "^3.1.8",

View File

@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",
@ -52,7 +52,7 @@
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
},
"node_modules/@hasura/dc-api-types": {
"version": "0.34.0",
"version": "0.35.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify": "^4.13.0",
"mathjs": "^11.0.0",
"pino-pretty": "^8.0.0",

View File

@ -96,6 +96,12 @@ const getUnaryComparisonOperatorEvaluator = (operator: UnaryComparisonOperator):
};
};
const getComparisonColumnSelector = (comparisonColumn: ComparisonColumn): string => {
if (typeof comparisonColumn.name === "string")
return comparisonColumn.name;
return comparisonColumn.name[0];
}
const prettyPrintComparisonColumn = (comparisonColumn: ComparisonColumn): string => {
return (comparisonColumn.path ?? []).concat(comparisonColumn.name).map(p => `[${p}]`).join(".");
}
@ -460,12 +466,12 @@ const addRelationshipFilterToQuery = (row: Record<string, RawScalarValue>, relat
const makeGetComparisonColumnValue = (parentQueryRowChain: Record<string, RawScalarValue>[]) => (comparisonColumn: ComparisonColumn, row: Record<string, RawScalarValue>): RawScalarValue => {
const path = comparisonColumn.path ?? [];
if (path.length === 0) {
return coerceUndefinedToNull(row[comparisonColumn.name]);
return coerceUndefinedToNull(row[getComparisonColumnSelector(comparisonColumn)]);
} else if (path.length === 1 && path[0] === "$") {
const queryRow = parentQueryRowChain.length === 0
? row
: parentQueryRowChain[0];
return coerceUndefinedToNull(queryRow[comparisonColumn.name]);
return coerceUndefinedToNull(queryRow[getComparisonColumnSelector(comparisonColumn)]);
} else {
throw new Error(`Unsupported path on ComparisonColumn: ${prettyPrintComparisonColumn(comparisonColumn)}`);
}

View File

@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify": "^4.13.0",
"fastify-metrics": "^9.2.1",
"nanoid": "^3.3.4",
@ -57,7 +57,7 @@
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
},
"node_modules/@hasura/dc-api-types": {
"version": "0.34.0",
"version": "0.35.0",
"license": "Apache-2.0",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@fastify/cors": "^8.1.0",
"@hasura/dc-api-types": "0.34.0",
"@hasura/dc-api-types": "0.35.0",
"fastify-metrics": "^9.2.1",
"fastify": "^4.13.0",
"nanoid": "^3.3.4",

View File

@ -228,12 +228,13 @@ function generateComparisonColumnFragment(comparisonColumn: ComparisonColumn, qu
const path = comparisonColumn.path ?? [];
const queryTablePrefix = queryTableAlias ? `${queryTableAlias}.` : '';
const currentTablePrefix = currentTableAlias ? `${currentTableAlias}.` : '';
const selector = getComparisonColumnSelector(comparisonColumn);
if (path.length === 0) {
return `${currentTablePrefix}${escapeIdentifier(comparisonColumn.name)}`
return `${currentTablePrefix}${escapeIdentifier(selector)}`
} else if (path.length === 1 && path[0] === "$") {
return `${queryTablePrefix}${escapeIdentifier(comparisonColumn.name)}`
return `${queryTablePrefix}${escapeIdentifier(selector)}`
} else {
throw new Error(`Unsupported path on ComparisonColumn: ${[...path, comparisonColumn.name].join(".")}`);
throw new Error(`Unsupported path on ComparisonColumn: ${[...path, selector].join(".")}`);
}
}
@ -882,3 +883,8 @@ type AnalysisEntry = {
detail: string
}
const getComparisonColumnSelector = (comparisonColumn: ComparisonColumn): string => {
if (typeof comparisonColumn.name === "string")
return comparisonColumn.name;
return comparisonColumn.name[0];
}

View File

@ -301,7 +301,7 @@ tests = describe "Basic Tests" $ do
(API.UnrelatedTable $ mkTableName "Employee")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "EmployeeId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "EmployeeId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 1) (API.ScalarType "number"))
)
)

View File

@ -165,27 +165,27 @@ tests = describe "Custom scalar parsing tests" $ do
( Set.fromList
[ ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyBooleanColumn") (ScalarType "MyBoolean"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyBooleanColumn") (ScalarType "MyBoolean"))
(ScalarValueComparison $ ScalarValue (J.Bool True) (ScalarType "MyBoolean")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyFloatColumn") (ScalarType "MyFloat"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyFloatColumn") (ScalarType "MyFloat"))
(ScalarValueComparison $ ScalarValue (J.Number 3.14) (ScalarType "MyFloat")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyStringColumn") (ScalarType "MyString"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyStringColumn") (ScalarType "MyString"))
(ScalarValueComparison $ ScalarValue (J.String "foo") (ScalarType "MyString")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyIDColumn") (ScalarType "MyID"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIDColumn") (ScalarType "MyID"))
(ScalarValueComparison $ ScalarValue (J.String "x") (ScalarType "MyID")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyIntColumn") (ScalarType "MyInt"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyIntColumn") (ScalarType "MyInt"))
(ScalarValueComparison $ ScalarValue (J.Number 42.0) (ScalarType "MyInt")),
ApplyBinaryComparisonOperator
Equal
(ComparisonColumn CurrentTable (ColumnName "MyAnythingColumn") (ScalarType "MyAnything"))
(ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "MyAnythingColumn") (ScalarType "MyAnything"))
(ScalarValueComparison $ ScalarValue (J.Object mempty) (ScalarType "MyAnything"))
]
)

View File

@ -204,11 +204,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 111) (API.ScalarType "number"))
],
API._dmoReturningFields =
@ -312,11 +312,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 90) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 112) (API.ScalarType "number"))
],
API._dmoReturningFields =

View File

@ -221,7 +221,7 @@ tests = do
Just
$ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "ArtistId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 2) (API.ScalarType "number")),
API._imoReturningFields =
mkFieldsMap

View File

@ -417,8 +417,8 @@ tests = describe "Object Relationships Tests" $ do
(API.RelatedTable $ API.RelationshipName "SupportRepForCustomers")
( API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string"))
)
& API.qOrderBy
?~ API.OrderBy
@ -429,8 +429,8 @@ tests = describe "Object Relationships Tests" $ do
$ API.Exists (API.RelatedTable $ API.RelationshipName "SupportRep")
$ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.ColumnName "Country") $ API.ScalarType "string"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string")
(API.AnotherColumnComparison (API.ComparisonColumn API.QueryTable (API.mkColumnSelector $ API.ColumnName "Country") $ API.ScalarType "string"))
)
mempty
)

View File

@ -239,18 +239,18 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "GenreId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "GenreId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 1) (API.ScalarType "number"))
],
API._umoPostUpdateCheck =
Just
$ API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 0) (API.ScalarType "number")),
API._umoReturningFields =
mkFieldsMap
@ -366,7 +366,7 @@ tests = do
Just
$ API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "UnitPrice") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 0) (API.ScalarType "number"))
let sharedReturning =
mkFieldsMap
@ -429,11 +429,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,
@ -469,11 +469,11 @@ tests = do
$ Set.fromList
[ API.ApplyBinaryComparisonOperator
API.Equal
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "AlbumId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number")),
API.ApplyBinaryComparisonOperator
API.GreaterThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "TrackId") $ API.ScalarType "number")
(API.ScalarValueComparison $ API.ScalarValue (J.Number 3) (API.ScalarType "number"))
],
API._umoPostUpdateCheck = sharedPostUpdateCheck,

View File

@ -9,6 +9,8 @@ module Hasura.Backends.DataConnector.API.V0.Expression
UnaryComparisonOperator (..),
ComparisonColumn (..),
ColumnPath (..),
ColumnSelector (..),
mkColumnSelector,
ComparisonValue (..),
)
where
@ -21,6 +23,8 @@ import Data.Aeson (FromJSON, ToJSON, Value)
import Data.Data (Data)
import Data.HashMap.Strict qualified as HashMap
import Data.Hashable (Hashable)
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NonEmpty
import Data.OpenApi (ToSchema)
import Data.Set (Set)
import Data.Text (Text)
@ -244,7 +248,7 @@ data ComparisonColumn = ComparisonColumn
{ -- | The path to the table that contains the specified column.
_ccPath :: ColumnPath,
-- | The name of the column
_ccName :: API.V0.ColumnName,
_ccName :: ColumnSelector,
-- | The scalar type of the column
_ccColumnType :: API.V0.ScalarType
}
@ -291,6 +295,20 @@ instance HasCodec ColumnPath where
CurrentTable -> []
QueryTable -> ["$"]
newtype ColumnSelector = ColumnSelector {unColumnSelector :: NonEmpty API.V0.ColumnName}
deriving stock (Eq, Ord, Show, Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ColumnSelector
deriving anyclass (Hashable, NFData)
instance HasCodec ColumnSelector where
codec = bimapCodec dec enc oneOrManyCodec
where
dec = maybe (Left "Unexpected empty list in ColumnSelector") (Right . ColumnSelector) . NonEmpty.nonEmpty
enc = NonEmpty.toList . unColumnSelector
mkColumnSelector :: API.V0.ColumnName -> ColumnSelector
mkColumnSelector = ColumnSelector . NonEmpty.singleton
-- | A serializable representation of comparison values used in comparisons inside 'Expression's.
data ComparisonValue
= -- | Allows a comparison to a column on the current table or another table

View File

@ -517,8 +517,8 @@ mkTestData schemaResponse testConfig =
_tdColumnInsertSchema = columnInsertSchema schemaResponse testConfig,
_tdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
_tdFindColumnScalarType = \tableName name -> findColumnScalarType schemaResponse tableName (formatColumnName testConfig $ API.ColumnName name),
_tdQueryComparisonColumn = API.ComparisonColumn API.QueryTable . formatColumnName testConfig . API.ColumnName,
_tdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . formatColumnName testConfig . API.ColumnName,
_tdQueryComparisonColumn = API.ComparisonColumn API.QueryTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName,
_tdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName,
_tdOrderByColumn = \targetPath name -> orderByColumn targetPath (formatColumnName testConfig $ API.ColumnName name)
}
where
@ -561,7 +561,7 @@ mkEdgeCasesTestData testConfig schemaResponse =
_ectdColumnField = columnField schemaResponse testConfig,
_ectdMkDefaultTableInsertSchema = mkDefaultTableInsertSchema schemaResponse testConfig edgeCasesSchemaTables,
_ectdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
_ectdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . formatColumnName testConfig . API.ColumnName
_ectdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . API.mkColumnSelector . formatColumnName testConfig . API.ColumnName
}
where
tableExists :: API.TableName -> Bool

View File

@ -47,7 +47,7 @@ spec TestData {..} (ScalarTypesCapabilities scalarTypesCapabilities) = describe
ApplyBinaryComparisonOperator
(CustomBinaryComparisonOperator (unName operatorName))
(_tdCurrentComparisonColumn (unColumnName columnName) columnType)
(AnotherColumnComparison $ ComparisonColumn CurrentTable argColumnName argType)
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector argColumnName) argType)
query =
queryRequest
& qrQuery . qWhere ?~ where'

View File

@ -130,7 +130,7 @@ spec testConfig API.Capabilities {} = describe "supports functions" $ preloadAge
whereClause =
API.ApplyBinaryComparisonOperator
API.LessThan
(API.ComparisonColumn API.CurrentTable (API.ColumnName "id") (API.ScalarType "number"))
(API.ComparisonColumn API.CurrentTable (API.mkColumnSelector $ API.ColumnName "id") (API.ScalarType "number"))
(API.ScalarValueComparison (API.ScalarValue (Number 10) (API.ScalarType "number")))
query' = Data.emptyQuery & qFields ?~ fields & qWhere ?~ whereClause & qLimit ?~ 2
authorRelationship =

View File

@ -37,6 +37,7 @@ module Autodocodec.Extended
fromEnvCodec,
optionalVersionField,
versionField,
oneOrManyCodec,
module Autodocodec,
)
where
@ -431,3 +432,12 @@ discriminatorBoolField name value =
-- reference.
fromEnvCodec :: JSONCodec Text
fromEnvCodec = object "FromEnv" $ requiredField' "from_env"
oneOrManyCodec :: forall a. (HasCodec a) => JSONCodec [a]
oneOrManyCodec =
matchChoiceCodec singletonCodec (codec @[a]) chooser
where
singletonCodec = dimapCodec pure id (codec @a)
chooser = \case
[x] -> Left x
xs -> Right xs

View File

@ -36,6 +36,7 @@ import Data.Bifunctor (Bifunctor (bimap))
import Data.ByteString qualified as BS
import Data.Has
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NonEmpty
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text qualified as T
@ -221,6 +222,20 @@ parseSessionVariable varName varType varValue = do
--------------------------------------------------------------------------------
newtype ColumnStack = ColumnStack [ColumnName]
emptyColumnStack :: ColumnStack
emptyColumnStack = ColumnStack []
pushColumn :: ColumnStack -> ColumnName -> ColumnStack
pushColumn (ColumnStack stack) columnName = ColumnStack $ columnName : stack
toColumnSelector :: ColumnStack -> ColumnName -> API.ColumnSelector
toColumnSelector (ColumnStack stack) columnName =
API.ColumnSelector $ NonEmpty.reverse $ Witch.from columnName :| fmap Witch.from stack
--------------------------------------------------------------------------------
translateBoolExpToExpression ::
( Has TableRelationships writerOutput,
Monoid writerOutput,
@ -233,7 +248,7 @@ translateBoolExpToExpression ::
AnnBoolExp 'DataConnector (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m (Maybe API.Expression)
translateBoolExpToExpression sessionVariables sourceName boolExp = do
removeAlwaysTrueExpression <$> translateBoolExp sessionVariables sourceName boolExp
removeAlwaysTrueExpression <$> translateBoolExp sessionVariables sourceName emptyColumnStack boolExp
translateBoolExp ::
( Has TableRelationships writerOutput,
@ -244,25 +259,29 @@ translateBoolExp ::
) =>
SessionVariables ->
TableRelationshipsKey ->
ColumnStack ->
AnnBoolExp 'DataConnector (UnpreparedValue 'DataConnector) ->
CPS.WriterT writerOutput m API.Expression
translateBoolExp sessionVariables sourceName = \case
translateBoolExp sessionVariables sourceName columnStack = \case
BoolAnd xs ->
mkIfZeroOrMany API.And . mapMaybe removeAlwaysTrueExpression <$> traverse (translateBoolExp' sourceName) xs
mkIfZeroOrMany API.And . mapMaybe removeAlwaysTrueExpression <$> traverse (translateBoolExp' sourceName columnStack) xs
BoolOr xs ->
mkIfZeroOrMany API.Or . mapMaybe removeAlwaysFalseExpression <$> traverse (translateBoolExp' sourceName) xs
mkIfZeroOrMany API.Or . mapMaybe removeAlwaysFalseExpression <$> traverse (translateBoolExp' sourceName columnStack) xs
BoolNot x ->
API.Not <$> (translateBoolExp' sourceName) x
BoolField (AVColumn c _redactionExp opExps) ->
API.Not <$> (translateBoolExp' sourceName columnStack) x
BoolField (AVColumn c _redactionExp opExps) -> do
-- TODO(redactionExp): Deal with the redaction expression
lift $ mkIfZeroOrMany API.And <$> traverse (translateOp sessionVariables (Witch.from $ ciColumn c) (Witch.from . columnTypeToScalarType $ ciType c)) opExps
let columnSelector = toColumnSelector columnStack $ ciColumn c
lift $ mkIfZeroOrMany API.And <$> traverse (translateOp sessionVariables columnSelector (Witch.from . columnTypeToScalarType $ ciType c)) opExps
BoolField (AVNestedObject NestedObjectInfo {..} nestedExp) ->
translateBoolExp' sourceName (pushColumn columnStack _noiColumn) nestedExp
BoolField (AVRelationship relationshipInfo (RelationshipFilters {rfTargetTablePermissions, rfFilter})) -> do
(relationshipName, API.Relationship {..}) <- recordTableRelationshipFromRelInfo sourceName relationshipInfo
-- TODO: How does this function keep track of the root table?
API.Exists (API.RelatedTable relationshipName) <$> translateBoolExp' (TableNameKey _rTargetTable) (BoolAnd [rfTargetTablePermissions, rfFilter])
API.Exists (API.RelatedTable relationshipName) <$> translateBoolExp' (TableNameKey _rTargetTable) emptyColumnStack (BoolAnd [rfTargetTablePermissions, rfFilter])
BoolExists GExists {..} ->
let tableName = Witch.from _geTable
in API.Exists (API.UnrelatedTable tableName) <$> translateBoolExp' (TableNameKey tableName) _geWhere
in API.Exists (API.UnrelatedTable tableName) <$> translateBoolExp' (TableNameKey tableName) emptyColumnStack _geWhere
where
translateBoolExp' = translateBoolExp sessionVariables
@ -288,7 +307,7 @@ removeAlwaysFalseExpression = \case
translateOp ::
(MonadError QErr m, MonadReader r m, Has API.ScalarTypesCapabilities r) =>
SessionVariables ->
API.ColumnName ->
API.ColumnSelector ->
API.ScalarType ->
OpExpG 'DataConnector (UnpreparedValue 'DataConnector) ->
m API.Expression
@ -358,7 +377,9 @@ translateOp sessionVariables columnName columnType opExp = do
let columnPath = case rootOrCurrent of
IsRoot -> API.QueryTable
IsCurrent -> API.CurrentTable
in API.ApplyBinaryComparisonOperator operator currentComparisonColumn (API.AnotherColumnComparison $ API.ComparisonColumn columnPath (Witch.from otherColumnName) columnType)
otherColumnSelector = API.mkColumnSelector $ Witch.from otherColumnName
in -- TODO(dmoverton): allow otherColumnName to refer to nested object fields.
API.ApplyBinaryComparisonOperator operator currentComparisonColumn (API.AnotherColumnComparison $ API.ComparisonColumn columnPath otherColumnSelector columnType)
inOperator :: Literal -> API.Expression
inOperator literal =

View File

@ -12,6 +12,7 @@ module Hasura.GraphQL.Schema.BoolExp
where
import Data.Has (getter)
import Data.HashMap.Strict qualified as HashMap
import Data.Text.Casing (GQLNameIdentifier)
import Data.Text.Casing qualified as C
import Data.Text.Extended
@ -137,7 +138,12 @@ boolExpInternal gqlName selectPermissions fieldInfos description memoizeKey mkAg
FIColumn (SCIScalarColumn columnInfo) ->
let redactionExp = fromMaybe NoRedaction $ getRedactionExprForColumn selectPermissions' (ciColumn columnInfo)
in lift $ fmap (AVColumn columnInfo redactionExp) <$> comparisonExps @b (ciType columnInfo)
FIColumn (SCIObjectColumn _) -> empty -- TODO(dmoverton)
FIColumn (SCIObjectColumn nestedObjectInfo@NestedObjectInfo {..}) -> do
SourceInfo {..} <- asks getter
logicalModelInfo <-
HashMap.lookup _noiType _siLogicalModels
`onNothing` throw500 ("Logical model " <> _noiType <<> " not found in source " <>> _siName)
lift $ fmap (AVNestedObject nestedObjectInfo) <$> logicalModelBoolExp logicalModelInfo
FIColumn (SCIArrayColumn _) -> empty -- TODO(dmoverton)
-- field_name: field_type_bool_exp
FIRelationship relationshipInfo -> do
@ -192,32 +198,30 @@ logicalModelBoolExp ::
SchemaT r m (Parser 'Input n (AnnBoolExp b (UnpreparedValue b)))
logicalModelBoolExp logicalModel = do
roleName <- retrieve scRole
case toFieldInfo (columnsFromFields $ _lmiFields logicalModel) of
Nothing -> throw500 $ "Error creating fields for logical model " <> tshow (_lmiName logicalModel)
Just fieldInfo -> do
let name = getLogicalModelName (_lmiName logicalModel)
gqlName = mkTableBoolExpTypeName (C.fromCustomName name)
selectPermissions = getSelPermInfoForLogicalModel roleName logicalModel
let fieldInfos = HashMap.elems $ logicalModelFieldsToFieldInfo $ _lmiFields logicalModel
name = getLogicalModelName (_lmiName logicalModel)
gqlName = mkTableBoolExpTypeName (C.fromCustomName name)
selectPermissions = getSelPermInfoForLogicalModel roleName logicalModel
-- Aggregation parsers let us say things like, "select all authors
-- with at least one article": they are predicates based on the
-- object's relationship with some other entity.
--
-- Currently, logical models can't be defined to have
-- relationships to other entities, and so they don't support
-- aggregation predicates.
--
-- If you're here because you've been asked to implement them, this
-- is where you want to put the parser.
mkAggPredParser = pure (pure mempty)
-- Aggregation parsers let us say things like, "select all authors
-- with at least one article": they are predicates based on the
-- object's relationship with some other entity.
--
-- Currently, logical models can't be defined to have
-- relationships to other entities, and so they don't support
-- aggregation predicates.
--
-- If you're here because you've been asked to implement them, this
-- is where you want to put the parser.
mkAggPredParser = pure (pure mempty)
memoizeKey = name
description =
G.Description
$ "Boolean expression to filter rows from the logical model for "
<> name
<<> ". All fields are combined with a logical 'AND'."
in boolExpInternal gqlName selectPermissions fieldInfo description memoizeKey mkAggPredParser
memoizeKey = name
description =
G.Description
$ "Boolean expression to filter rows from the logical model for "
<> name
<<> ". All fields are combined with a logical 'AND'."
boolExpInternal gqlName selectPermissions fieldInfos description memoizeKey mkAggPredParser
-- |
-- > input type_bool_exp {

View File

@ -12,11 +12,11 @@ import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
import Data.Text.Extended (ToTxt (toTxt))
import Hasura.LogicalModel.Cache
import Hasura.LogicalModel.NullableScalarType (NullableScalarType (..))
import Hasura.LogicalModel.Types (LogicalModelField (..), LogicalModelType (..), LogicalModelTypeScalar (..))
import Hasura.LogicalModel.Types (LogicalModelField (..), LogicalModelType (..), LogicalModelTypeArray (..), LogicalModelTypeReference (..), LogicalModelTypeScalar (..))
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp (AnnRedactionExp (..), gBoolExpTrue)
import Hasura.RQL.Types.Backend (Backend (..))
import Hasura.RQL.Types.Column (ColumnInfo (..), ColumnMutability (..), ColumnType (..), StructuredColumnInfo (..), fromCol)
import Hasura.RQL.Types.Column (ColumnInfo (..), ColumnMutability (..), ColumnType (..), NestedArrayInfo (..), NestedObjectInfo (..), StructuredColumnInfo (..), fromCol)
import Hasura.RQL.Types.Permission (AllowedRootFields (..))
import Hasura.RQL.Types.Roles (RoleName, adminRoleName)
import Hasura.Table.Cache (FieldInfo (..), FieldInfoMap, RolePermInfo (..), SelPermInfo (..))
@ -55,9 +55,7 @@ logicalModelToColumnInfo :: forall b. (Backend b) => Int -> (Column b, NullableS
logicalModelToColumnInfo i (column, NullableScalarType {..}) = do
name <- G.mkName (toTxt column)
pure
$
-- TODO(dmoverton): handle object and array columns
SCIScalarColumn
$ SCIScalarColumn
$ ColumnInfo
{ ciColumn = column,
ciName = name,
@ -76,11 +74,51 @@ logicalModelFieldsToFieldInfo ::
logicalModelFieldsToFieldInfo =
HashMap.fromList
. fmap (bimap (fromCol @b) FIColumn)
. fromMaybe mempty
. traverseWithIndex
(\i (column, lmf) -> (,) column <$> logicalModelToColumnInfo i (column, lmf))
. catMaybes
. zipWith (\i (column, lmf) -> (column,) <$> logicalModelFieldToStructuredColumnInfo i lmf) [0 ..]
. InsOrdHashMap.toList
. columnsFromFields
where
logicalModelFieldToStructuredColumnInfo :: Int -> LogicalModelField b -> Maybe (StructuredColumnInfo b)
logicalModelFieldToStructuredColumnInfo i LogicalModelField {..} = go lmfType
where
go = \case
LogicalModelTypeScalar LogicalModelTypeScalarC {..} -> do
name <- G.mkName (toTxt lmfName)
pure
$ SCIScalarColumn
$ ColumnInfo
{ ciColumn = lmfName,
ciName = name,
ciPosition = i,
ciType = ColumnScalar lmtsScalar,
ciIsNullable = lmtsNullable,
ciDescription = G.Description <$> lmfDescription,
ciMutability = ColumnMutability {_cmIsInsertable = False, _cmIsUpdatable = False}
}
LogicalModelTypeArray LogicalModelTypeArrayC {..} -> do
supportsNestedObjects <- eitherToMaybe $ backendSupportsNestedObjects @b
arrayColumnInfo <- go lmtaArray
pure
$ SCIArrayColumn
$ NestedArrayInfo
{ _naiSupportsNestedArrays = supportsNestedObjects,
_naiIsNullable = lmtaNullable,
_naiColumnInfo = arrayColumnInfo
}
LogicalModelTypeReference LogicalModelTypeReferenceC {..} -> do
supportsNestedObjects <- eitherToMaybe $ backendSupportsNestedObjects @b
name <- G.mkName (toTxt lmfName)
pure
$ SCIObjectColumn
$ NestedObjectInfo
{ _noiSupportsNestedObjects = supportsNestedObjects,
_noiColumn = lmfName,
_noiName = name,
_noiType = lmtrReference,
_noiIsNullable = lmtrNullable,
_noiDescription = G.Description <$> lmfDescription,
_noiMutability = ColumnMutability {_cmIsInsertable = False, _cmIsUpdatable = False}
}
getSelPermInfoForLogicalModel ::
(Backend b) =>

View File

@ -499,6 +499,7 @@ instance
-- This type is parameterized over the type of leaf values, the values on which we operate.
data AnnBoolExpFld (backend :: BackendType) leaf
= AVColumn (ColumnInfo backend) (AnnRedactionExp backend leaf) [OpExpG backend leaf]
| AVNestedObject (NestedObjectInfo backend) (AnnBoolExp backend leaf)
| AVRelationship
(RelInfo backend)
(RelationshipFilters backend leaf)
@ -555,6 +556,10 @@ instance
( K.fromText $ toTxt $ ciColumn pci,
toJSON (pci, object . pure . toJSONKeyValue <$> opExps)
)
AVNestedObject noi boolExp ->
( K.fromText $ toTxt $ _noiColumn noi,
toJSON (noi, boolExp)
)
AVRelationship ri filters ->
( K.fromText $ relNameToTxt $ riName ri,
toJSON (ri, toJSON filters)

View File

@ -789,6 +789,8 @@ getLogicalModelColExpDeps source logicalModelLocation = \case
AVRelationship {} -> []
AVComputedField _ -> []
AVAggregationPredicates _ -> []
AVNestedObject NestedObjectInfo {..} boolExp ->
getLogicalModelBoolExpDeps source (LMLLogicalModel _noiType) boolExp
AVColumn colInfo _redactionExp opExps -> do
let columnName :: Column b
columnName = ciColumn colInfo
@ -851,6 +853,8 @@ getColExpDeps bexp = do
colDepReason = bool DRSessionVariable DROnType $ any hasStaticExp opExps
colDep = mkColDep @b colDepReason source currTable columnName
in (colDep :) <$> getOpExpDeps opExps
AVNestedObject NestedObjectInfo {..} boolExp ->
pure $ getLogicalModelBoolExpDeps source (LMLLogicalModel _noiType) boolExp
AVRelationship relInfo RelationshipFilters {rfTargetTablePermissions, rfFilter} ->
case riTarget relInfo of
RelTargetNativeQuery _ -> error "getColExpDeps RelTargetNativeQuery"

View File

@ -68,7 +68,7 @@ spec = do
describe "ComparisonColumn" $ do
testToFromJSONToSchema
(ComparisonColumn QueryTable (ColumnName "column_name") (ScalarType "string"))
(ComparisonColumn QueryTable (mkColumnSelector $ ColumnName "column_name") (ScalarType "string"))
[aesonQQ|{"path": ["$"], "name": "column_name", "column_type": "string"}|]
jsonOpenApiProperties genComparisonColumn
@ -80,10 +80,17 @@ spec = do
$ testToFromJSONToSchema CurrentTable [aesonQQ|[]|]
jsonOpenApiProperties genColumnPath
describe "ColumnSelector" $ do
describe "single column selector"
$ testToFromJSONToSchema (ColumnSelector [ColumnName "foo"]) [aesonQQ|"foo"|]
describe "nested path selector"
$ testToFromJSONToSchema (ColumnSelector [ColumnName "foo", ColumnName "bar"]) [aesonQQ|["foo","bar"]|]
jsonOpenApiProperties genColumnSelector
describe "ComparisonValue" $ do
describe "AnotherColumnComparison"
$ testToFromJSONToSchema
(AnotherColumnComparison $ ComparisonColumn CurrentTable (ColumnName "my_column_name") (ScalarType "string"))
(AnotherColumnComparison $ ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string"))
[aesonQQ|{"type": "column", "column": {"name": "my_column_name", "column_type": "string"}}|]
describe "ScalarValueComparison"
$ testToFromJSONToSchema
@ -112,7 +119,7 @@ spec = do
jsonOpenApiProperties genExistsInTable
describe "Expression" $ do
let comparisonColumn = ComparisonColumn CurrentTable (ColumnName "my_column_name") (ScalarType "string")
let comparisonColumn = ComparisonColumn CurrentTable (mkColumnSelector $ ColumnName "my_column_name") (ScalarType "string")
let scalarValue = ScalarValueComparison $ ScalarValue (String "scalar value") (ScalarType "string")
let scalarValues = [String "scalar value"]
let unaryComparisonExpression = ApplyUnaryComparisonOperator IsNull comparisonColumn
@ -247,13 +254,17 @@ genComparisonColumn :: (MonadGen m, GenBase m ~ Identity) => m ComparisonColumn
genComparisonColumn =
ComparisonColumn
<$> genColumnPath
<*> genColumnName
<*> genColumnSelector
<*> genScalarType
genColumnPath :: (MonadGen m) => m ColumnPath
genColumnPath =
Gen.element [CurrentTable, QueryTable]
genColumnSelector :: (MonadGen m) => m ColumnSelector
genColumnSelector =
ColumnSelector <$> Gen.nonEmpty defaultRange genColumnName
genComparisonValue :: (MonadGen m, GenBase m ~ Identity) => m ComparisonValue
genComparisonValue =
Gen.choice