diff --git a/crates/squawk_ide/src/generated/extensions/postgis.sql b/crates/squawk_ide/src/generated/extensions/postgis.sql index 284b381c..17fcc0cf 100644 --- a/crates/squawk_ide/src/generated/extensions/postgis.sql +++ b/crates/squawk_ide/src/generated/extensions/postgis.sql @@ -24,6 +24,17 @@ create type public.gidx; -- size: 65, align: 8 create type public.spheroid; +create type public.geometry_dump as ( + path integer[], + geom geometry +); + +create type public.valid_detail as ( + valid boolean, + reason character varying, + location geometry +); + create table public.spatial_ref_sys ( srid integer, auth_name character varying(256), diff --git a/crates/squawk_ide/src/generated/extensions/tablefunc.sql b/crates/squawk_ide/src/generated/extensions/tablefunc.sql new file mode 100644 index 00000000..96a6663d --- /dev/null +++ b/crates/squawk_ide/src/generated/extensions/tablefunc.sql @@ -0,0 +1,59 @@ +-- squawk-ignore-file +-- pg version: 18.3 +-- update via: +-- cargo xtask sync-builtins + +create type public.tablefunc_crosstab_2 as ( + row_name text, + category_1 text, + category_2 text +); + +create type public.tablefunc_crosstab_3 as ( + row_name text, + category_1 text, + category_2 text, + category_3 text +); + +create type public.tablefunc_crosstab_4 as ( + row_name text, + category_1 text, + category_2 text, + category_3 text, + category_4 text +); + +create function public.connectby(text, text, text, text, integer) returns SETOF record + language c; + +create function public.connectby(text, text, text, text, integer, text) returns SETOF record + language c; + +create function public.connectby(text, text, text, text, text, integer) returns SETOF record + language c; + +create function public.connectby(text, text, text, text, text, integer, text) returns SETOF record + language c; + +create function public.crosstab(text) returns SETOF record + language c; + +create function public.crosstab(text, integer) returns SETOF record + language c; + +create function public.crosstab(text, text) returns SETOF record + language c; + +create function public.crosstab2(text) returns SETOF tablefunc_crosstab_2 + language c; + +create function public.crosstab3(text) returns SETOF tablefunc_crosstab_3 + language c; + +create function public.crosstab4(text) returns SETOF tablefunc_crosstab_4 + language c; + +create function public.normal_rand(integer, double precision, double precision) returns SETOF double precision + language c; + diff --git a/crates/xtask/src/sync_builtins.rs b/crates/xtask/src/sync_builtins.rs index b330c583..a9f6c76f 100644 --- a/crates/xtask/src/sync_builtins.rs +++ b/crates/xtask/src/sync_builtins.rs @@ -5,10 +5,20 @@ use std::path::Path; use std::process::{Command, Stdio}; use anyhow::{Context, Result, bail}; -use serde::Deserialize; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Deserializer}; use crate::path::project_root; +fn deserialize_json_string<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: DeserializeOwned, +{ + let s = String::deserialize(deserializer)?; + serde_json::from_str(&s).map_err(serde::de::Error::custom) +} + const PG_TEMP_SCHEMA_SQL: &str = "create schema pg_temp;\n\n"; #[derive(Deserialize)] @@ -129,7 +139,8 @@ struct RelationQuery { relkind: String, description: String, extension_name: String, - columns: String, + #[serde(deserialize_with = "deserialize_json_string")] + columns: Vec, } impl Query for RelationQuery { @@ -212,6 +223,49 @@ order by n.nspname, p.proname, pg_get_function_arguments(p.oid), pg_get_function "; } +#[derive(Deserialize)] +struct CompositeTypeQuery { + schema: String, + name: String, + description: String, + extension_name: String, + #[serde(deserialize_with = "deserialize_json_string")] + columns: Vec, +} + +impl Query for CompositeTypeQuery { + const QUERY: &'static str = r" +select + n.nspname as schema, + t.typname as name, + coalesce(d.description, '') as description, + coalesce(ext.extname, 'builtins') as extension_name, + json_agg( + json_build_object( + 'name', a.attname, + 'data_type', format_type(a.atttypid, a.atttypmod) + ) + order by a.attnum + ) as columns +from pg_type t + join pg_namespace n on n.oid = t.typnamespace + join pg_class c on c.oid = t.typrelid + join pg_attribute a on a.attrelid = c.oid + left join pg_description d on d.objoid = t.oid and d.classoid = 'pg_type'::regclass + left join pg_depend dep on dep.classid = 'pg_type'::regclass and dep.objid = t.oid and dep.objsubid = 0 and dep.deptype = 'e' + left join pg_extension ext on ext.oid = dep.refobjid +where n.nspname not like 'pg_temp%' + and n.nspname not like 'pg_toast%' + and (n.nspname != 'public' or ext.extname is not null) + and t.typtype = 'c' + and c.relkind = 'c' + and a.attnum > 0 + and not a.attisdropped +group by t.oid, n.nspname, t.typname, d.description, ext.extname +order by n.nspname, t.typname, t.oid; +"; +} + #[derive(Deserialize)] struct OperatorQuery { schema: String, @@ -282,6 +336,7 @@ begin 'plpgsql', 'postgis', 'postgres_fdw', + 'tablefunc', 'vector' ] loop execute format('create extension if not exists %I', extension_name); @@ -363,6 +418,31 @@ impl WriteSql for RangeTypeDef { } } +struct CompositeTypeDef { + schema: String, + name: String, + description: String, + columns: Vec, +} + +impl WriteSql for CompositeTypeDef { + fn write_sql(&self, f: &mut W) -> io::Result<()> { + write_description(f, &self.description)?; + writeln!(f, "create type {}.{} as (", self.schema, self.name)?; + for (index, column) in self.columns.iter().enumerate() { + let comma = if index + 1 < self.columns.len() { + "," + } else { + "" + }; + writeln!(f, " {} {}{comma}", column.name, column.data_type)?; + } + writeln!(f, ");")?; + writeln!(f)?; + Ok(()) + } +} + struct TableDef { schema: String, name: String, @@ -552,6 +632,7 @@ struct Module { schemas: Vec, types: Vec, range_types: Vec, + composite_types: Vec, tables: Vec, views: Vec, functions: Vec, @@ -572,6 +653,10 @@ impl Module { range_type.write_sql(f)?; } + for composite_type in &self.composite_types { + composite_type.write_sql(f)?; + } + for table in &self.tables { table.write_sql(f)?; } @@ -663,21 +748,35 @@ fn query_types(modules: &mut BTreeMap) -> Result<()> { Ok(()) } +fn query_composite_types(modules: &mut BTreeMap) -> Result<()> { + for row in CompositeTypeQuery::run()? { + modules + .entry(row.extension_name) + .or_default() + .composite_types + .push(CompositeTypeDef { + columns: row.columns, + description: row.description, + name: row.name, + schema: row.schema, + }); + } + + Ok(()) +} + fn query_relations(modules: &mut BTreeMap) -> Result<()> { for row in RelationQuery::run()? { - let columns: Vec = - serde_json::from_str(&row.columns).context("expected valid column json")?; - let module = modules.entry(row.extension_name).or_default(); match row.relkind.as_str() { "r" => module.tables.push(TableDef { - columns, + columns: row.columns, description: row.description, name: row.name, schema: row.schema, }), "v" => module.views.push(ViewDef { - columns, + columns: row.columns, description: row.description, name: row.name, schema: row.schema, @@ -759,6 +858,7 @@ pub(crate) fn sync_builtins() -> Result<()> { query_schemas(&mut modules)?; query_types(&mut modules)?; + query_composite_types(&mut modules)?; query_relations(&mut modules)?; query_functions(&mut modules)?; query_operators(&mut modules)?;