Skip to content

Commit cfd0d29

Browse files
plcplcGil Mizrahi
and
Gil Mizrahi
authored
Support introspecting composite types (#391)
### What This PR adds the ability of the introspection query to detect composite types, meaning user-defined record types that do not arise from a table but exist as standalone definitions. This even discovered a typo in the previous, manually crafted composite type metadata. Support for cockroachdb is limited here until cockroachdb/cockroach#109675 is released. ### How **Introspection Query** now captures composite types, filtering out the tables. Making metadata json out of these is quite similar to how we already did tables, so no huge surprises here. **Occurring types logic** now becomes somewhat more complex because we can no longer simply look at which type names are used in the collections we track. A composite type occurring in say, a table column, may refer to scalar types that don't occur anywhere else, and even other composite types. Occurring type discovery thus becomes an iterative procedure rather than a single pass. --------- Co-authored-by: Gil Mizrahi <[email protected]>
1 parent 7fbfa17 commit cfd0d29

13 files changed

+1160
-160
lines changed

crates/configuration/src/version3/mod.rs

+105-7
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,16 @@ pub async fn introspect(
111111
.instrument(info_span!("Run introspection query"))
112112
.await?;
113113

114-
let (tables, aggregate_functions, comparison_operators) = async {
114+
let (tables, aggregate_functions, comparison_operators, composite_types) = async {
115115
let tables: metadata::TablesInfo = serde_json::from_value(row.get(0))?;
116116

117117
let aggregate_functions: metadata::AggregateFunctions = serde_json::from_value(row.get(1))?;
118118

119119
let metadata::ComparisonOperators(mut comparison_operators): metadata::ComparisonOperators =
120120
serde_json::from_value(row.get(2))?;
121121

122+
let composite_types: metadata::CompositeTypes = serde_json::from_value(row.get(3))?;
123+
122124
// We need to include `in` as a comparison operator in the schema, and since it is syntax, it is not introspectable.
123125
// Instead, we will check if the scalar type defines an equals operator and if yes, we will insert the `_in` operator
124126
// as well.
@@ -146,17 +148,24 @@ pub async fn introspect(
146148
tables,
147149
aggregate_functions,
148150
metadata::ComparisonOperators(comparison_operators),
151+
composite_types,
149152
))
150153
}
151154
.instrument(info_span!("Decode introspection result"))
152155
.await?;
153156

154-
let scalar_types = occurring_scalar_types(
155-
&tables,
156-
&args.metadata.native_queries,
157-
&args.metadata.aggregate_functions,
157+
let (scalar_types, composite_types) = transitively_occurring_types(
158+
occurring_scalar_types(
159+
&tables,
160+
&args.metadata.native_queries,
161+
&args.metadata.aggregate_functions,
162+
),
163+
occurring_composite_types(&tables, &args.metadata.native_queries),
164+
composite_types,
158165
);
159166

167+
// We filter our comparison operators and aggregate functions to only include those relevant to
168+
// types that may actually occur in the schema.
160169
let relevant_comparison_operators =
161170
filter_comparison_operators(&scalar_types, comparison_operators);
162171
let relevant_aggregate_functions =
@@ -170,14 +179,103 @@ pub async fn introspect(
170179
native_queries: args.metadata.native_queries,
171180
aggregate_functions: relevant_aggregate_functions,
172181
comparison_operators: relevant_comparison_operators,
173-
composite_types: args.metadata.composite_types,
182+
composite_types,
174183
},
175184
introspection_options: args.introspection_options,
176185
mutations_version: args.mutations_version,
177186
})
178187
}
179188

180-
/// Collect all the types that can occur in the metadata. This is a bit circumstantial. A better
189+
/// Collect all the composite types that can occur in the metadata.
190+
pub fn occurring_composite_types(
191+
tables: &metadata::TablesInfo,
192+
native_queries: &metadata::NativeQueries,
193+
) -> BTreeSet<String> {
194+
let tables_column_types = tables
195+
.0
196+
.values()
197+
.flat_map(|v| v.columns.values().map(|c| &c.r#type));
198+
let native_queries_column_types = native_queries
199+
.0
200+
.values()
201+
.flat_map(|v| v.columns.values().map(|c| &c.r#type));
202+
let native_queries_arguments_types = native_queries
203+
.0
204+
.values()
205+
.flat_map(|v| v.arguments.values().map(|c| &c.r#type));
206+
207+
tables_column_types
208+
.chain(native_queries_column_types)
209+
.chain(native_queries_arguments_types)
210+
.filter_map(|t| match t {
211+
metadata::Type::CompositeType(ref t) => Some(t.clone()),
212+
metadata::Type::ArrayType(t) => match **t {
213+
metadata::Type::CompositeType(ref t) => Some(t.clone()),
214+
metadata::Type::ArrayType(_) | metadata::Type::ScalarType(_) => None,
215+
},
216+
metadata::Type::ScalarType(_) => None,
217+
})
218+
.collect::<BTreeSet<String>>()
219+
}
220+
221+
// Since array types and composite types may refer to other types we have to transitively discover
222+
// the full set of types that are relevant to the schema.
223+
pub fn transitively_occurring_types(
224+
mut occurring_scalar_types: BTreeSet<metadata::ScalarType>,
225+
occurring_type_names: BTreeSet<String>,
226+
mut composite_types: metadata::CompositeTypes,
227+
) -> (BTreeSet<metadata::ScalarType>, metadata::CompositeTypes) {
228+
let mut discovered_type_names = occurring_type_names.clone();
229+
230+
for t in &occurring_type_names {
231+
match composite_types.0.get(t) {
232+
None => (),
233+
Some(ct) => {
234+
for f in ct.fields.values() {
235+
match &f.r#type {
236+
metadata::Type::CompositeType(ct2) => {
237+
discovered_type_names.insert(ct2.to_string());
238+
}
239+
metadata::Type::ScalarType(t) => {
240+
occurring_scalar_types.insert(t.clone());
241+
}
242+
metadata::Type::ArrayType(arr_ty) => match **arr_ty {
243+
metadata::Type::CompositeType(ref ct2) => {
244+
discovered_type_names.insert(ct2.to_string());
245+
}
246+
metadata::Type::ScalarType(ref t) => {
247+
occurring_scalar_types.insert(t.clone());
248+
}
249+
metadata::Type::ArrayType(_) => {
250+
// This case is impossible, because we do not support nested arrays
251+
}
252+
},
253+
}
254+
}
255+
}
256+
}
257+
}
258+
259+
// Since 'discovered_type_names' only grows monotonically starting from 'occurring_type_names'
260+
// we just have to compare the number of elements to know if new types have been discovered.
261+
if discovered_type_names.len() == occurring_type_names.len() {
262+
// Iterating over occurring types discovered no new types
263+
composite_types
264+
.0
265+
.retain(|t, _| occurring_type_names.contains(t));
266+
(occurring_scalar_types, composite_types)
267+
} else {
268+
// Iterating over occurring types did discover new types,
269+
// so we keep on going.
270+
transitively_occurring_types(
271+
occurring_scalar_types,
272+
discovered_type_names,
273+
composite_types,
274+
)
275+
}
276+
}
277+
278+
/// Collect all the scalar types that can occur in the metadata. This is a bit circumstantial. A better
181279
/// approach is likely to record scalar type names directly in the metadata via version2.sql.
182280
pub fn occurring_scalar_types(
183281
tables: &metadata::TablesInfo,

0 commit comments

Comments
 (0)