Skip to content
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
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Support for introspecting prefix-functions as comparison operators ([#223](https://github.com/hasura/ndc-postgres/pull/223))
- Support queries without fields specified. ([#14](https://github.com/hasura/ndc-postgres/pull/209))
- Query transactions now run in read-only mode, and allow transaction isolation level configuration. ([#209](https://github.com/hasura/ndc-postgres/pull/209), [#212](https://github.com/hasura/ndc-postgres/pull/212))
- Support variables as arguments to native queries ([#211](https://github.com/hasura/ndc-postgres/pull/211))
Expand Down
151 changes: 149 additions & 2 deletions crates/connectors/ndc-postgres/src/configuration/version2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ pub struct ConfigureOptions {
/// Which version of the generated mutation procedures to include in the schema response
#[serde(default)]
pub mutations_version: Option<metadata::mutations::MutationsVersion>,
/// Which prefix functions (i.e., non-infix operators) to generate introspection metadata for.
///
/// This list will accept any boolean-returning function taking two concrete scalar types as
/// arguments.
///
/// The default includes comparisons for various build-in types as well as those of PostGIS.
#[serde(default = "default_introspect_prefix_function_comparison_operators")]
pub introspect_prefix_function_comparison_operators: Vec<String>,
}

impl Default for ConfigureOptions {
Expand All @@ -76,12 +84,146 @@ impl Default for ConfigureOptions {
excluded_schemas: version1::default_excluded_schemas(),
unqualified_schemas: version1::default_unqualified_schemas(),
comparison_operator_mapping: version1::default_comparison_operator_mapping(),
mutations_version: None, // we'll change this to `Some(MutationsVersions::V1)` when we
// want to "release" this behaviour
// we'll change this to `Some(MutationsVersions::V1)` when we
// want to "release" this behaviour
mutations_version: None,
introspect_prefix_function_comparison_operators:
default_introspect_prefix_function_comparison_operators(),
}
}
}

fn default_introspect_prefix_function_comparison_operators() -> Vec<String> {
vec![
"box_above".to_string(),
"box_below".to_string(),
"box_contain".to_string(),
"box_contain_pt".to_string(),
"box_contained".to_string(),
"box_left".to_string(),
"box_overabove".to_string(),
"box_overbelow".to_string(),
"box_overlap".to_string(),
"box_overleft".to_string(),
"box_overright".to_string(),
"box_right".to_string(),
"box_same".to_string(),
"circle_above".to_string(),
"circle_below".to_string(),
"circle_contain".to_string(),
"circle_contain_pt".to_string(),
"circle_contained".to_string(),
"circle_left".to_string(),
"circle_overabove".to_string(),
"circle_overbelow".to_string(),
"circle_overlap".to_string(),
"circle_overleft".to_string(),
"circle_overright".to_string(),
"circle_right".to_string(),
"circle_same".to_string(),
"contains_2d".to_string(),
"equals".to_string(),
"geography_overlaps".to_string(),
"geometry_above".to_string(),
"geometry_below".to_string(),
"geometry_contained_3d".to_string(),
"geometry_contains".to_string(),
"geometry_contains_3d".to_string(),
"geometry_contains_nd".to_string(),
"geometry_left".to_string(),
"geometry_overabove".to_string(),
"geometry_overbelow".to_string(),
"geometry_overlaps".to_string(),
"geometry_overlaps_3d".to_string(),
"geometry_overlaps_nd".to_string(),
"geometry_overleft".to_string(),
"geometry_overright".to_string(),
"geometry_right".to_string(),
"geometry_same".to_string(),
"geometry_same_3d".to_string(),
"geometry_same_nd".to_string(),
"geometry_within".to_string(),
"geometry_within_nd".to_string(),
"inet_same_family".to_string(),
"inter_lb".to_string(),
"inter_sb".to_string(),
"inter_sl".to_string(),
"is_contained_2d".to_string(),
"ishorizontal".to_string(),
"isparallel".to_string(),
"isperp".to_string(),
"isvertical".to_string(),
"jsonb_contained".to_string(),
"jsonb_contains".to_string(),
"jsonb_exists".to_string(),
"jsonb_path_exists_opr".to_string(),
"jsonb_path_match_opr".to_string(),
"line_intersect".to_string(),
"line_parallel".to_string(),
"line_perp".to_string(),
"lseg_intersect".to_string(),
"lseg_parallel".to_string(),
"lseg_perp".to_string(),
"network_overlap".to_string(),
"network_sub".to_string(),
"network_sup".to_string(),
"on_pb".to_string(),
"on_pl".to_string(),
"on_ppath".to_string(),
"on_ps".to_string(),
"on_sb".to_string(),
"on_sl".to_string(),
"overlaps_2d".to_string(),
"path_contain_pt".to_string(),
"path_inter".to_string(),
"point_above".to_string(),
"point_below".to_string(),
"point_horiz".to_string(),
"point_left".to_string(),
"point_right".to_string(),
"point_vert".to_string(),
"poly_above".to_string(),
"poly_below".to_string(),
"poly_contain".to_string(),
"poly_contain_pt".to_string(),
"poly_contained".to_string(),
"poly_left".to_string(),
"poly_overabove".to_string(),
"poly_overbelow".to_string(),
"poly_overlap".to_string(),
"poly_overleft".to_string(),
"poly_overright".to_string(),
"poly_right".to_string(),
"poly_same".to_string(),
"pt_contained_poly".to_string(),
"st_3dintersects".to_string(),
"st_contains".to_string(),
"st_containsproperly".to_string(),
"st_coveredby".to_string(),
"st_covers".to_string(),
"st_crosses".to_string(),
"st_disjoint".to_string(),
"st_equals".to_string(),
"st_intersects".to_string(),
"st_isvalid".to_string(),
"st_orderingequals".to_string(),
"st_overlaps".to_string(),
"st_relatematch".to_string(),
"st_touches".to_string(),
"st_within".to_string(),
"starts_with".to_string(),
"ts_match_qv".to_string(),
"ts_match_tq".to_string(),
"ts_match_tt".to_string(),
"ts_match_vq".to_string(),
"tsq_mcontained".to_string(),
"tsq_mcontains".to_string(),
"xmlexists".to_string(),
"xmlvalidate".to_string(),
"xpath_exists".to_string(),
]
}

/// Validate the user configuration.
pub async fn validate_raw_configuration(
config: RawConfiguration,
Expand Down Expand Up @@ -118,6 +260,11 @@ pub async fn configure(
.bind(
serde_json::to_value(args.configure_options.comparison_operator_mapping.clone())
.map_err(|e| connector::UpdateConfigurationError::Other(e.into()))?,
)
.bind(
args.configure_options
.introspect_prefix_function_comparison_operators
.clone(),
);

let row = connection
Expand Down
74 changes: 71 additions & 3 deletions crates/connectors/ndc-postgres/src/configuration/version2.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
-- query with arguments set.

-- DEALLOCATE ALL; -- Or use 'DEALLOCATE configuration' between reloads
-- PREPARE configuration(varchar[], varchar[], jsonb) AS
-- PREPARE configuration(varchar[], varchar[], jsonb, varchar[]) AS

WITH
-- The overall structure of this query is a CTE (i.e. 'WITH .. SELECT')
Expand Down Expand Up @@ -370,6 +370,61 @@ WITH

),

-- Comparison procedures are any entries in 'pg_proc' that happen to be
-- binary functions that return booleans. We also require, for the sake of
-- simplicity, that these functions be non-variadic (i.e. no default values).
-- Within this CTE, we attempt to generate a table of comparison procedures
-- to match the shape of the 'comparison_operators'.
comparison_procedures AS
(
WITH
fixity_2_predicates_with_type_id AS
(
SELECT
proc.proname AS operator_name,
proc.proargtypes[0] as argument1_type_id,
proc.proargtypes[1] as argument2_type_id
FROM
pg_catalog.pg_proc AS proc
INNER JOIN scalar_types
AS ret_type
ON (ret_type.type_id = proc.prorettype)
WHERE
ret_type.type_name = 'bool'
-- We check that we only consider procedures which take two regular
-- arguments.
AND cardinality(proc.proargtypes) = 2
AND proc.prokind = 'f'
AND proc.provariadic = 0
AND proc.pronargdefaults = 0
),

fixity_2_predicates_with_type_name AS
(
SELECT
p.operator_name,
arg1_type.type_name AS argument1_type,
arg2_type.type_name AS argument2_type
FROM
fixity_2_predicates_with_type_id AS p
INNER JOIN scalar_types
AS arg1_type
ON (argument1_type_id = arg1_type.type_id)
INNER JOIN scalar_types
AS arg2_type
ON (argument2_type_id = arg2_type.type_id)
)
SELECT
*,
false AS is_infix
FROM fixity_2_predicates_with_type_name
WHERE
-- Include only procedures that are explicitly selected.
-- This is controlled by the
-- 'introspectPrefixFunctionComparisonOperators' configuration option.
operator_name = ANY ($4)
),

-- Operators are recorded across 'pg_proc', pg_operator, and 'pg_aggregate', see
-- https://www.postgresql.org/docs/current/catalog-pg-proc.html,
-- https://www.postgresql.org/docs/current/catalog-pg-operator.html and
Expand All @@ -378,7 +433,7 @@ WITH
--
-- In PostgreSQL, operators and aggregation functions each relate to a `pg_proc`
-- procedure. On CockroachDB, however, they are independent.
comparison_operators AS
comparison_infix_operators AS
(
SELECT
op.oprname AS operator_name,
Expand All @@ -405,6 +460,18 @@ WITH
ORDER BY op.oprname
),

-- Here, we reunite our binary infix procedures and our binary prefix
-- procedures under the umbrella of 'comparison_operators'. We do this
-- here so that we can treat them uniformly form this point on.
-- Specifically, we generate all the various type coercion permutations
-- for both in 'comparison_operators_cast_extended'.
comparison_operators AS
(
SELECT * FROM comparison_infix_operators
UNION
SELECT * FROM comparison_procedures
),

implicit_casts AS
(
SELECT
Expand Down Expand Up @@ -1083,5 +1150,6 @@ FROM
-- {"operatorName": "!~", "exposedName": "_nregex"},
-- {"operatorName": "~*", "exposedName": "_iregex"},
-- {"operatorName": "!~*", "exposedName": "_niregex"}
-- ]'::jsonb
-- ]'::jsonb,
-- '{box_above,box_below}'::varchar[]
-- );
Loading