Skip to content

Commit f4bc041

Browse files
committed
Merge remote-tracking branch 'origin/main' into gavin/snowflake-native-query-support
2 parents ced5171 + c2bba32 commit f4bc041

File tree

5 files changed

+122
-44
lines changed

5 files changed

+122
-44
lines changed

.github/workflows/build-connectors-action.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- "snowflake/*"
77
- "mysql/*"
88
- "oracle/*"
9+
- "phoenix/*"
910

1011
jobs:
1112
docker-build:

ndc-connector-mysql/.hasura-connector/connector-metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
packagingDefinition:
22
type: PrebuiltDockerImage
3-
dockerImage: "ghcr.io/hasura/ndc-jvm-mysql:v1.0.3"
3+
dockerImage: "ghcr.io/hasura/ndc-jvm-mysql:v1.0.4"
44
supportedEnvironmentVariables:
55
- name: JDBC_URL
66
description: "The JDBC URL to connect to the database"

ndc-connector-mysql/src/main/kotlin/io/hasura/mysql/JSONGenerator.kt

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,16 @@ import org.jooq.impl.SQLDataType
1818
object JsonQueryGenerator : BaseQueryGenerator() {
1919

2020
override fun forEachQueryRequestToSQL(request: QueryRequest): Select<*> {
21-
return DSL.with(buildVarsCTE(request))
22-
.select()
23-
.from(queryRequestToSQLInternal(request), DSL.table(DSL.name("vars")))
21+
return DSL
22+
.with(buildVarsCTE(request))
23+
.select(
24+
jsonArrayAgg(
25+
buildJSONSelectionForQueryRequest(request)
26+
)
27+
)
28+
.from(
29+
DSL.table(DSL.name("vars"))
30+
)
2431
}
2532

2633
override fun queryRequestToSQL(request: QueryRequest): Select<*> {
@@ -30,23 +37,12 @@ object JsonQueryGenerator : BaseQueryGenerator() {
3037
fun queryRequestToSQLInternal(
3138
request: QueryRequest,
3239
): SelectSelectStep<*> {
33-
// If the QueryRequest "collection" references the name of a Native Query defined in the configuration.json,
34-
// we need to prefix the generated query with a CTE named identically to the Native Query, containing the Native Query itself
35-
val isNativeQuery = ConnectorConfiguration.Loader.config.nativeQueries.containsKey(request.collection)
36-
37-
return if (isNativeQuery) {
38-
mkNativeQueryCTE(request).select(
39-
jsonArrayAgg(
40-
buildJSONSelectionForQueryRequest(request)
41-
)
42-
)
43-
} else {
44-
DSL.select(
45-
jsonArrayAgg(
46-
buildJSONSelectionForQueryRequest(request)
47-
)
40+
// JOOQ is smart enough to not generate CTEs if there are no native queries
41+
return mkNativeQueryCTEs(request).select(
42+
jsonArrayAgg(
43+
buildJSONSelectionForQueryRequest(request)
4844
)
49-
}
45+
)
5046
}
5147

5248
fun buildJSONSelectionForQueryRequest(
@@ -197,27 +193,6 @@ object JsonQueryGenerator : BaseQueryGenerator() {
197193
)
198194
}
199195

200-
fun renderNativeQuerySQL(
201-
nativeQuery: NativeQueryInfo,
202-
arguments: Map<String, Argument>
203-
): String {
204-
val sql = nativeQuery.sql
205-
val parts = sql.parts
206-
207-
return parts.joinToString("") { part ->
208-
when (part) {
209-
is NativeQueryPart.Text -> part.value
210-
is NativeQueryPart.Parameter -> {
211-
val argument = arguments[part.value] ?: error("Argument ${part.value} not found")
212-
when (argument) {
213-
is Argument.Literal -> argument.value.toString()
214-
else -> error("Only literals are supported in Native Queries in this version")
215-
}
216-
}
217-
}
218-
}
219-
}
220-
221196
fun jsonArrayAgg(field: JSONObjectNullStep<*>) = CustomField.of("mysql_json_arrayagg", SQLDataType.JSON) {
222197
it.visit(DSL.field("json_arrayagg({0})", field))
223198
}
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
packagingDefinition:
22
type: PrebuiltDockerImage
3-
dockerImage: "ghcr.io/hasura/ndc-jvm-oracle:v1.0.2"
3+
dockerImage: "ghcr.io/hasura/ndc-jvm-oracle:v1.0.3"
44
supportedEnvironmentVariables:
55
- name: JDBC_URL
66
description: "The JDBC URL to connect to the database"
@@ -11,8 +11,7 @@ commands:
1111
docker run \
1212
-e HASURA_PLUGIN_CONNECTOR_CONTEXT_PATH \
1313
-v ${HASURA_PLUGIN_CONNECTOR_CONTEXT_PATH}:/app/output \
14-
ghcr.io/hasura/ndc-jvm-cli:v0.1.1 update $JDBC_URL \
14+
ghcr.io/hasura/ndc-jvm-cli:v0.1.3 update $JDBC_URL \
1515
--database ORACLE \
1616
--schemas $JDBC_SCHEMAS \
1717
--outfile /app/output/configuration.json
18-

ndc-sqlgen/src/main/kotlin/io/hasura/ndc/sqlgen/BaseQueryGenerator.kt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,109 @@ abstract class BaseQueryGenerator : BaseGenerator {
2525
throw NotImplementedError("Mutation not supported for this data source")
2626
}
2727

28+
protected fun findAllNativeQueries(request: QueryRequest): Set<String> {
29+
val nativeQueries = mutableSetOf<String>()
30+
val config = ConnectorConfiguration.Loader.config
31+
32+
// Helper function to check if a collection is a native query
33+
fun checkAndAddNativeQuery(collection: String) {
34+
if (config.nativeQueries.containsKey(collection)) {
35+
nativeQueries.add(collection)
36+
}
37+
}
38+
39+
// Check main collection
40+
checkAndAddNativeQuery(request.collection)
41+
42+
// Check relationships
43+
request.collection_relationships.values.forEach { rel ->
44+
checkAndAddNativeQuery(rel.target_collection)
45+
}
46+
47+
// Recursive function to check predicates
48+
fun checkPredicates(expression: Expression?) {
49+
when (expression) {
50+
is Expression.Exists -> {
51+
when (val collection = expression.in_collection) {
52+
is ExistsInCollection.Related -> {
53+
// Check related collection from relationship
54+
val rel = request.collection_relationships[collection.relationship]
55+
?: error("Relationship ${collection.relationship} not found")
56+
checkAndAddNativeQuery(rel.target_collection)
57+
}
58+
is ExistsInCollection.Unrelated -> {
59+
checkAndAddNativeQuery(collection.collection)
60+
}
61+
}
62+
// Recursively check the predicate within exists
63+
checkPredicates(expression.predicate)
64+
}
65+
is Expression.And -> expression.expressions.forEach { checkPredicates(it) }
66+
is Expression.Or -> expression.expressions.forEach { checkPredicates(it) }
67+
is Expression.Not -> checkPredicates(expression.expression)
68+
else -> {} // Other expression types don't reference collections
69+
}
70+
}
71+
72+
// Check predicates in the main query
73+
checkPredicates(request.query.predicate)
74+
75+
// Check predicates in relationship fields
76+
request.query.fields?.values?.forEach { field ->
77+
if (field is IRField.RelationshipField) {
78+
checkPredicates(field.query.predicate)
79+
}
80+
}
81+
82+
return nativeQueries
83+
}
84+
85+
fun mkNativeQueryCTEs(
86+
request: QueryRequest
87+
): org.jooq.WithStep {
88+
val config = ConnectorConfiguration.Loader.config
89+
var nativeQueries = findAllNativeQueries(request)
90+
91+
if (nativeQueries.isEmpty()) {
92+
// JOOQ is smart enough to not generate CTEs if there are no native queries
93+
return DSL.with()
94+
}
95+
96+
fun renderNativeQuerySQL(
97+
nativeQuery: NativeQueryInfo,
98+
arguments: Map<String, Argument>
99+
): String {
100+
val sql = nativeQuery.sql
101+
val parts = sql.parts
102+
103+
return parts.joinToString("") { part ->
104+
when (part) {
105+
is NativeQueryPart.Text -> part.value
106+
is NativeQueryPart.Parameter -> {
107+
val argument = arguments[part.value] ?: error("Argument ${part.value} not found")
108+
when (argument) {
109+
is Argument.Literal -> argument.value.toString()
110+
else -> error("Only literals are supported in Native Queries in this version")
111+
}
112+
}
113+
}
114+
}
115+
}
116+
117+
val withStep = DSL.with()
118+
nativeQueries.forEach { collectionName ->
119+
withStep.with(DSL.name(collectionName))
120+
.`as`(DSL.resultQuery(
121+
renderNativeQuerySQL(
122+
config.nativeQueries[collectionName]!!,
123+
request.arguments
124+
)
125+
))
126+
}
127+
128+
return withStep
129+
}
130+
28131
fun mkNativeQueryCTE(
29132
request: QueryRequest
30133
): org.jooq.WithStep {

0 commit comments

Comments
 (0)