Skip to content

Commit 07cd594

Browse files
authored
Merge pull request #36 from hasura/gavin/oracle-native-queries
Oracle NQ support
2 parents 9d1130b + 78a6741 commit 07cd594

File tree

4 files changed

+393
-113
lines changed

4 files changed

+393
-113
lines changed

ndc-connector-oracle/configuration.json

+58-1
Original file line numberDiff line numberDiff line change
@@ -666,5 +666,62 @@
666666
}
667667
} ],
668668
"functions" : [ ],
669-
"nativeQueries" : { }
669+
"nativeQueries" : {
670+
"oracle_native_query_inline": {
671+
"sql": {
672+
"parts": [
673+
{
674+
"type": "text",
675+
"value": "SELECT 1 AS result FROM DUAL"
676+
}
677+
]
678+
},
679+
"columns": {
680+
"result": {
681+
"type": "named",
682+
"name": "INT"
683+
}
684+
},
685+
"arguments": {},
686+
"description": ""
687+
},
688+
"ArtistById_Oracle": {
689+
"sql": {
690+
"parts": [
691+
{
692+
"type": "text",
693+
"value": "SELECT * FROM CHINOOK.ARTIST WHERE ARTISTID = "
694+
},
695+
{
696+
"type": "parameter",
697+
"value": "ARTISTID"
698+
}
699+
]
700+
},
701+
"columns": {
702+
"ARTISTID": {
703+
"type": "named",
704+
"name": "INT"
705+
},
706+
"NAME": {
707+
"type": "nullable",
708+
"underlying_type": {
709+
"type": "named",
710+
"name": "STRING"
711+
}
712+
}
713+
},
714+
"arguments": {
715+
"ARTISTID": {
716+
"description": null,
717+
"type": {
718+
"type": "named",
719+
"name": "INT"
720+
}
721+
}
722+
},
723+
"description": null,
724+
"isProcedure": false
725+
}
726+
}
670727
}

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

+128-111
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.hasura.oracle
33
import io.hasura.ndc.common.ConnectorConfiguration
44
import io.hasura.ndc.common.NDCScalar
55
import io.hasura.ndc.ir.*
6+
import io.hasura.ndc.ir.Type
67
import io.hasura.ndc.ir.Field.ColumnField
78
import io.hasura.ndc.ir.Field as IRField
89
import io.hasura.ndc.sqlgen.BaseQueryGenerator
@@ -19,23 +20,88 @@ object JsonQueryGenerator : BaseQueryGenerator() {
1920
}
2021

2122
override fun queryRequestToSQL(request: QueryRequest): Select<*> {
22-
return queryRequestToSQLInternal2(request)
23+
return mkNativeQueryCTEs(request).select(
24+
DSL.jsonArrayAgg(
25+
buildJSONSelectionForQueryRequest(request)
26+
).returning(
27+
SQLDataType.CLOB
28+
)
29+
)
2330
}
2431

25-
fun queryRequestToSQLInternal2(
32+
fun buildJSONSelectionForQueryRequest(
2633
request: QueryRequest,
2734
parentTable: String? = null,
28-
parentRelationship: Relationship? = null,
29-
): SelectHavingStep<Record1<JSON>> {
30-
val isRootQuery = parentRelationship == null
31-
32-
return DSL.select(
33-
DSL.jsonObject(
34-
buildList {
35-
if (!request.query.fields.isNullOrEmpty()) {
36-
add(
37-
DSL.jsonEntry(
38-
"rows",
35+
parentRelationship: Relationship? = null
36+
): JSONObjectNullStep<*> {
37+
38+
val baseSelection = DSL.select(
39+
DSL.table(DSL.name(request.collection)).asterisk()
40+
).from(
41+
if (request.query.predicate == null) {
42+
DSL.table(DSL.name(request.collection))
43+
} else {
44+
val table = DSL.table(DSL.name(request.collection))
45+
val requiredJoinTables = collectRequiredJoinTablesForWhereClause(
46+
where = request.query.predicate!!,
47+
collectionRelationships = request.collection_relationships
48+
)
49+
requiredJoinTables.foldIndexed(table) { index, acc, relationship ->
50+
val parentTable = if (index == 0) {
51+
request.collection
52+
} else {
53+
requiredJoinTables.elementAt(index - 1).target_collection
54+
}
55+
56+
val joinTable = DSL.table(DSL.name(relationship.target_collection))
57+
acc.join(joinTable).on(
58+
mkJoinWhereClause(
59+
sourceTable = parentTable,
60+
parentRelationship = relationship
61+
)
62+
)
63+
}
64+
}
65+
).apply {
66+
if (request.query.predicate != null) {
67+
where(getWhereConditions(request))
68+
}
69+
if (parentRelationship != null) {
70+
where(
71+
mkJoinWhereClause(
72+
sourceTable = parentTable ?: error("parentTable is null"),
73+
parentRelationship = parentRelationship
74+
)
75+
)
76+
}
77+
if (request.query.order_by != null) {
78+
orderBy(
79+
translateIROrderByField(
80+
orderBy = request.query.order_by,
81+
currentCollection = getTableName(request.collection),
82+
relationships = request.collection_relationships
83+
)
84+
)
85+
}
86+
if (request.query.limit != null) {
87+
limit(request.query.limit)
88+
}
89+
if (request.query.offset != null) {
90+
offset(request.query.offset)
91+
}
92+
}.apply {
93+
addJoinsRequiredForOrderByFields(this, request)
94+
}.asTable(
95+
DSL.name(getTableName(request.collection))
96+
)
97+
98+
return DSL.jsonObject(
99+
buildList {
100+
if (!request.query.fields.isNullOrEmpty()) {
101+
add(
102+
DSL.jsonEntry(
103+
"rows",
104+
DSL.select(
39105
DSL.jsonArrayAgg(
40106
DSL.jsonObject(
41107
(request.query.fields ?: emptyMap()).map { (alias, field) ->
@@ -55,7 +121,7 @@ object JsonQueryGenerator : BaseQueryGenerator() {
55121
request.collection_relationships[field.relationship]
56122
?: error("Relationship ${field.relationship} not found")
57123

58-
val subQuery = queryRequestToSQLInternal2(
124+
val subQuery = buildJSONSelectionForQueryRequest(
59125
parentTable = request.collection,
60126
parentRelationship = relationship,
61127
request = QueryRequest(
@@ -70,9 +136,8 @@ object JsonQueryGenerator : BaseQueryGenerator() {
70136
DSL.jsonEntry(
71137
alias,
72138
DSL.coalesce(
73-
DSL.select(
74-
subQuery.asField<Any>(alias)
75-
), DSL.jsonObject(
139+
DSL.select(subQuery),
140+
DSL.jsonObject(
76141
DSL.jsonEntry(
77142
"rows",
78143
DSL.jsonArray().returning(SQLDataType.CLOB)
@@ -84,109 +149,37 @@ object JsonQueryGenerator : BaseQueryGenerator() {
84149
}
85150
}
86151
).returning(SQLDataType.CLOB)
87-
).apply {
88-
if (request.query.order_by != null) {
89-
orderBy(
90-
translateIROrderByField(
91-
orderBy = request.query.order_by,
92-
currentCollection = getTableName(request.collection),
93-
relationships = request.collection_relationships
94-
)
95-
)
96-
}
97-
}.returning(SQLDataType.CLOB)
152+
).returning(SQLDataType.CLOB)
153+
).from(
154+
baseSelection
98155
)
99156
)
100-
}
101-
if (!request.query.aggregates.isNullOrEmpty()) {
102-
add(
103-
DSL.jsonEntry(
104-
"aggregates",
157+
)
158+
}
159+
if (!request.query.aggregates.isNullOrEmpty()) {
160+
add(
161+
DSL.jsonEntry(
162+
"aggregates",
163+
DSL.select(
105164
DSL.jsonObject(
106165
(request.query.aggregates ?: emptyMap()).map { (alias, aggregate) ->
107166
DSL.jsonEntry(
108167
alias,
109168
getAggregatejOOQFunction(aggregate)
110169
)
111170
}
112-
)
113-
)
114-
)
115-
}
116-
}
117-
).returning(SQLDataType.CLOB).let {
118-
when {
119-
isRootQuery -> DSL.jsonArrayAgg(it).returning(SQLDataType.CLOB)
120-
else -> it
121-
}
122-
}
123-
).from(
124-
DSL.select(
125-
DSL.table(DSL.name(request.collection))
126-
.asterisk()
127-
).from(
128-
run<Table<Record>> {
129-
val table = DSL.table(DSL.name(request.collection))
130-
if (request.query.predicate == null) {
131-
table
132-
} else {
133-
val requiredJoinTables = collectRequiredJoinTablesForWhereClause(
134-
where = request.query.predicate!!,
135-
collectionRelationships = request.collection_relationships
136-
)
137-
138-
requiredJoinTables.foldIndexed(table) { index, acc, relationship ->
139-
val parentTable = if (index == 0) {
140-
request.collection
141-
} else {
142-
requiredJoinTables.elementAt(index - 1).target_collection
143-
}
144-
145-
val joinTable = DSL.table(DSL.name(relationship.target_collection))
146-
acc.join(joinTable).on(
147-
mkJoinWhereClause(
148-
sourceTable = parentTable,
149-
parentRelationship = relationship
150-
)
171+
).returning(SQLDataType.CLOB)
172+
).from(
173+
baseSelection
151174
)
152-
}
153-
}
154-
}
155-
).apply {
156-
if (request.query.predicate != null) {
157-
where(getWhereConditions(request))
158-
}
159-
if (parentRelationship != null) {
160-
where(
161-
mkJoinWhereClause(
162-
sourceTable = parentTable ?: error("parentTable is null"),
163-
parentRelationship = parentRelationship
164-
)
165-
)
166-
}
167-
if (request.query.order_by != null) {
168-
orderBy(
169-
translateIROrderByField(
170-
orderBy = request.query.order_by,
171-
currentCollection = getTableName(request.collection),
172-
relationships = request.collection_relationships
173175
)
174176
)
175177
}
176-
if (request.query.limit != null) {
177-
limit(request.query.limit)
178-
}
179-
if (request.query.offset != null) {
180-
offset(request.query.offset)
181-
}
182-
}.asTable(
183-
DSL.name(getTableName(request.collection))
184-
)
185-
).groupBy(
186-
DSL.nullCondition()
187-
)
178+
}
179+
).returning(SQLDataType.CLOB) as JSONObjectNullStep<*>
188180
}
189-
181+
182+
190183
fun collectRequiredJoinTablesForWhereClause(
191184
where: Expression,
192185
collectionRelationships: Map<String, Relationship>,
@@ -223,13 +216,37 @@ object JsonQueryGenerator : BaseQueryGenerator() {
223216
private fun columnTypeTojOOQType(collection: String, field: ColumnField): org.jooq.DataType<out Any> {
224217
val connectorConfig = ConnectorConfiguration.Loader.config
225218

226-
val table = connectorConfig.tables.find { it.tableName == collection }
227-
?: error("Table $collection not found in connector configuration")
219+
val collectionIsTable = connectorConfig.tables.any { it.tableName == collection }
220+
val collectionIsNativeQuery = connectorConfig.nativeQueries.containsKey(collection)
221+
222+
if (!collectionIsTable && !collectionIsNativeQuery) {
223+
error("Collection $collection not found in connector configuration")
224+
}
225+
226+
val scalarType = when {
227+
collectionIsTable -> {
228+
val table = connectorConfig.tables.find { it.tableName == collection }
229+
?: error("Table $collection not found in connector configuration")
230+
231+
val column = table.columns.find { it.name == field.column }
232+
?: error("Column ${field.column} not found in table $collection")
228233

229-
val column = table.columns.find { it.name == field.column }
230-
?: error("Column ${field.column} not found in table $collection")
234+
OracleJDBCSchemaGenerator.mapScalarType(column.type, column.numeric_scale)
235+
}
236+
237+
collectionIsNativeQuery -> {
238+
val nativeQuery = connectorConfig.nativeQueries[collection]
239+
?: error("Native query $collection not found in connector configuration")
240+
241+
val column = nativeQuery.columns[field.column]
242+
?: error("Column ${field.column} not found in native query $collection")
243+
244+
OracleJDBCSchemaGenerator.mapScalarType(Type.extractBaseType(column), null)
245+
}
246+
247+
else -> error("Collection $collection not found in connector configuration")
248+
}
231249

232-
val scalarType = OracleJDBCSchemaGenerator.mapScalarType(column.type, column.numeric_scale)
233250
return when (scalarType) {
234251
NDCScalar.BOOLEAN -> SQLDataType.BOOLEAN
235252
NDCScalar.INT -> SQLDataType.INTEGER

ndc-ir/src/main/kotlin/io/hasura/ndc/ir/Schema.kt

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ sealed interface Type {
4747
data class Nullable(val underlying_type: Type) : Type
4848
@JsonTypeName("array")
4949
data class Array(val element_type: Type) : Type
50+
51+
companion object {
52+
fun extractBaseType(type: Type): String {
53+
return when (type) {
54+
is Type.Nullable -> extractBaseType(type.underlying_type)
55+
is Type.Array -> extractBaseType(type.element_type)
56+
is Type.Named -> type.name
57+
}
58+
}
59+
}
5060
}
5161

5262
data class ArgumentInfo (

0 commit comments

Comments
 (0)