Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly handle NativeQueries in Relationships #27

Merged
merged 2 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,12 @@ object JsonQueryGenerator : BaseQueryGenerator() {
fun queryRequestToSQLInternal(
request: QueryRequest,
): SelectSelectStep<*> {
// If the QueryRequest "collection" references the name of a Native Query defined in the configuration.json,
// we need to prefix the generated query with a CTE named identically to the Native Query, containing the Native Query itself
val isNativeQuery = ConnectorConfiguration.Loader.config.nativeQueries.containsKey(request.collection)

return if (isNativeQuery) {
mkNativeQueryCTE(request).select(
jsonArrayAgg(
buildJSONSelectionForQueryRequest(request)
)
)
} else {
DSL.select(
jsonArrayAgg(
buildJSONSelectionForQueryRequest(request)
)
// JOOQ is smart enough to not generate CTEs if there are no native queries
return mkNativeQueryCTEs(request).select(
codedmart marked this conversation as resolved.
Show resolved Hide resolved
jsonArrayAgg(
buildJSONSelectionForQueryRequest(request)
)
}
)
}

fun buildJSONSelectionForQueryRequest(
Expand Down
107 changes: 104 additions & 3 deletions ndc-sqlgen/src/main/kotlin/io/hasura/ndc/sqlgen/BaseQueryGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,109 @@ abstract class BaseQueryGenerator : BaseGenerator {
throw NotImplementedError("Mutation not supported for this data source")
}

protected fun findAllNativeQueries(request: QueryRequest): Set<String> {
val nativeQueries = mutableSetOf<String>()
val config = ConnectorConfiguration.Loader.config

// Helper function to check if a collection is a native query
fun checkAndAddNativeQuery(collection: String) {
if (config.nativeQueries.containsKey(collection)) {
nativeQueries.add(collection)
}
}

// Check main collection
checkAndAddNativeQuery(request.collection)

// Check relationships
request.collection_relationships.values.forEach { rel ->
checkAndAddNativeQuery(rel.target_collection)
}

// Recursive function to check predicates
fun checkPredicates(expression: Expression?) {
when (expression) {
is Expression.Exists -> {
when (val collection = expression.in_collection) {
is ExistsInCollection.Related -> {
// Check related collection from relationship
val rel = request.collection_relationships[collection.relationship]
?: error("Relationship ${collection.relationship} not found")
checkAndAddNativeQuery(rel.target_collection)
}
is ExistsInCollection.Unrelated -> {
checkAndAddNativeQuery(collection.collection)
}
}
// Recursively check the predicate within exists
checkPredicates(expression.predicate)
}
is Expression.And -> expression.expressions.forEach { checkPredicates(it) }
is Expression.Or -> expression.expressions.forEach { checkPredicates(it) }
is Expression.Not -> checkPredicates(expression.expression)
else -> {} // Other expression types don't reference collections
}
}

// Check predicates in the main query
checkPredicates(request.query.predicate)

// Check predicates in relationship fields
request.query.fields?.values?.forEach { field ->
if (field is IRField.RelationshipField) {
checkPredicates(field.query.predicate)
}
}

return nativeQueries
}

fun mkNativeQueryCTEs(
codedmart marked this conversation as resolved.
Show resolved Hide resolved
request: QueryRequest
): org.jooq.WithStep {
val config = ConnectorConfiguration.Loader.config
var nativeQueries = findAllNativeQueries(request)

if (nativeQueries.isEmpty()) {
// JOOQ is smart enough to not generate CTEs if there are no native queries
return DSL.with()
}

fun renderNativeQuerySQL(
nativeQuery: NativeQueryInfo,
arguments: Map<String, Argument>
): String {
val sql = nativeQuery.sql
val parts = sql.parts

return parts.joinToString("") { part ->
when (part) {
is NativeQueryPart.Text -> part.value
is NativeQueryPart.Parameter -> {
val argument = arguments[part.value] ?: error("Argument ${part.value} not found")
when (argument) {
is Argument.Literal -> argument.value.toString()
else -> error("Only literals are supported in Native Queries in this version")
}
}
}
}
}

val withStep = DSL.with()
nativeQueries.forEach { collectionName ->
withStep.with(DSL.name(collectionName))
.`as`(DSL.resultQuery(
renderNativeQuerySQL(
config.nativeQueries[collectionName]!!,
request.arguments
)
))
}

return withStep
}

fun mkNativeQueryCTE(
request: QueryRequest
): org.jooq.WithStep {
Expand Down Expand Up @@ -446,9 +549,7 @@ abstract class BaseQueryGenerator : BaseGenerator {
e = where,
request
)
} ?: DSL.noCondition())).also {
println("Where conditions: $it")
}
} ?: DSL.noCondition()))
}

protected fun getDefaultAggregateJsonEntries(aggregates: Map<String, Aggregate>?): Field<*> {
Expand Down