@@ -6,6 +6,14 @@ import { nanoid } from '@deditor-app/shared'
66import * as schema from '@deditor-app/shared-schemas'
77import { postgresInformationSchemaColumns , postgresPgCatalogPgAm , postgresPgCatalogPgAttribute , postgresPgCatalogPgClass , postgresPgCatalogPgIndex , postgresPgCatalogPgNamespace , postgresPgCatalogPgType } from '@deditor-app/shared-schemas'
88import { PGlite } from '@electric-sql/pglite'
9+ import { bloom } from '@electric-sql/pglite/contrib/bloom'
10+ import { citext } from '@electric-sql/pglite/contrib/citext'
11+ import { cube } from '@electric-sql/pglite/contrib/cube'
12+ import { earthdistance } from '@electric-sql/pglite/contrib/earthdistance'
13+ import { hstore } from '@electric-sql/pglite/contrib/hstore'
14+ import { ltree } from '@electric-sql/pglite/contrib/ltree'
15+ import { seg } from '@electric-sql/pglite/contrib/seg'
16+ import { vector } from '@electric-sql/pglite/vector'
917import { useLogg } from '@guiiai/logg'
1018import { and , eq , gt , ne , not , notExists , notLike , or , sql } from 'drizzle-orm'
1119import { alias } from 'drizzle-orm/pg-core'
@@ -53,7 +61,21 @@ export function registerPGLiteDatabaseDialect(window: BrowserWindow) {
5361 try {
5462 const parsedDSN = new URL ( dsn )
5563
56- const pgliteClient = new PGlite ( decodeURIComponent ( String ( parsedDSN . searchParams . get ( 'dataDir' ) ) ) )
64+ const pgliteClient = new PGlite (
65+ decodeURIComponent ( String ( parsedDSN . searchParams . get ( 'dataDir' ) ) ) ,
66+ {
67+ extensions : {
68+ vector,
69+ bloom,
70+ citext,
71+ cube,
72+ earthdistance,
73+ hstore,
74+ ltree,
75+ seg,
76+ } ,
77+ } ,
78+ )
5779
5880 const pgDrizzle = drizzle ( pgliteClient , { schema } )
5981 const dbSessionId = nanoid ( )
@@ -262,6 +284,232 @@ export function registerPGLiteDatabaseDialect(window: BrowserWindow) {
262284 throw err
263285 }
264286 } )
287+
288+ defineIPCHandler < PGLiteMethods > ( window , 'databaseLocalPGLite' , 'listUserDefinedTypes' )
289+ . handle ( async ( _ , { databaseSessionId } ) => {
290+ /**
291+ * 2013 Approach to list user-defined types in PostgreSQL.
292+ * For pgvector, this method is still valid.
293+ * But I haven't touched any of the other UDT in scenarios.
294+ *
295+ * Thanks to
296+ *
297+ * postgresql - Display user-defined types and their details - Database Administrators Stack Exchange
298+ * @link {https://dba.stackexchange.com/a/35510}
299+ *
300+ * SELECT
301+ * n.nspname AS schema,
302+ * pg_catalog.format_type ( t.oid, NULL ) AS name,
303+ * t.typname AS internal_name,
304+ * CASE
305+ * WHEN t.typrelid != 0
306+ * THEN CAST ( 'tuple' AS pg_catalog.text )
307+ * WHEN t.typlen < 0
308+ * THEN CAST ( 'var' AS pg_catalog.text )
309+ * ELSE CAST ( t.typlen AS pg_catalog.text )
310+ * END AS size,
311+ * pg_catalog.array_to_string (
312+ * ARRAY(
313+ * SELECT e.enumlabel
314+ * FROM pg_catalog.pg_enum e
315+ * WHERE e.enumtypid = t.oid
316+ * ORDER BY e.oid
317+ * ), E'\n'
318+ * ) AS elements,
319+ * -- https://www.postgresql.org/docs/9.5/functions-info.html
320+ * pg_catalog.obj_description(t.oid, 'pg_type') AS description
321+ * FROM pg_catalog.pg_type t
322+ * LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
323+ * WHERE
324+ * (
325+ * t.typrelid = 0
326+ * OR (
327+ * SELECT c.relkind = 'c'
328+ * FROM pg_catalog.pg_class c
329+ * WHERE c.oid = t.typrelid
330+ * )
331+ * ) AND
332+ * NOT EXISTS (
333+ * SELECT 1
334+ * FROM pg_catalog.pg_type el
335+ * WHERE
336+ * el.oid = t.typelem AND
337+ * el.typarray = t.oid
338+ * ) AND
339+ * -- Ignores the built-in types
340+ * n.nspname <> 'pg_catalog' AND
341+ * -- Ignores the built-in information_schema comes along types
342+ * n.nspname <> 'information_schema' AND
343+ * pg_catalog.pg_type_is_visible ( t.oid )
344+ * ORDER BY 1, 2;
345+ */
346+
347+ /**
348+ * Thanks to
349+ *
350+ * postgresql - Display user-defined types and their details - Database Administrators Stack Exchange
351+ * @link {https://dba.stackexchange.com/a/35510}
352+ *
353+ * WITH
354+ * types AS (
355+ * SELECT
356+ * n.nspname,
357+ * pg_catalog.format_type(t.oid, NULL) AS obj_name,
358+ * CASE
359+ * WHEN t.typrelid != 0 THEN CAST ( 'tuple' AS pg_catalog.text )
360+ * WHEN t.typlen < 0 THEN CAST ( 'var' AS pg_catalog.text )
361+ * ELSE CAST ( t.typlen AS pg_catalog.text )
362+ * END AS obj_type,
363+ * coalesce(pg_catalog.obj_description(t.oid, 'pg_type'), '') AS description
364+ * FROM pg_catalog.pg_type t
365+ * JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
366+ * WHERE (
367+ * t.typrelid = 0 OR
368+ * (
369+ * SELECT c.relkind = 'c'
370+ * FROM pg_catalog.pg_class c
371+ * WHERE c.oid = t.typrelid
372+ * )
373+ * ) AND
374+ * NOT EXISTS (
375+ * SELECT 1
376+ * FROM pg_catalog.pg_type el
377+ * WHERE
378+ * el.oid = t.typelem AND
379+ * el.typarray = t.oid
380+ * ) AND
381+ * n.nspname <> 'pg_catalog' AND
382+ * n.nspname <> 'information_schema' AND
383+ * n.nspname !~ '^pg_toast'
384+ * ),
385+ * cols AS (
386+ * SELECT
387+ * n.nspname::text AS schema_name,
388+ * pg_catalog.format_type(t.oid, NULL) AS obj_name,
389+ * a.attname::text AS column_name,
390+ * pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
391+ * a.attnotnull AS is_required,
392+ * a.attnum AS ordinal_position,
393+ * pg_catalog.col_description(a.attrelid, a.attnum) AS description
394+ * FROM pg_catalog.pg_attribute a
395+ * JOIN pg_catalog.pg_type t ON a.attrelid = t.typrelid
396+ * JOIN pg_catalog.pg_namespace n ON ( n.oid = t.typnamespace )
397+ * JOIN types ON ( types.nspname = n.nspname AND types.obj_name = pg_catalog.format_type(t.oid, NULL) )
398+ * WHERE
399+ * a.attnum > 0 AND
400+ * NOT a.attisdropped
401+ * )
402+ *
403+ * SELECT
404+ * cols.schema_name,
405+ * cols.obj_name,
406+ * cols.column_name,
407+ * cols.data_type,
408+ * cols.ordinal_position,
409+ * cols.is_required,
410+ * coalesce(cols.description, '') AS description
411+ * FROM cols
412+ * ORDER BY cols.schema_name, cols.obj_name, cols.ordinal_position;
413+ */
414+ if ( ! databaseSessions . has ( databaseSessionId ) ) {
415+ throw new Error ( 'Database session ID not found in session map, please connect to the database first.' )
416+ }
417+
418+ try {
419+ const dbSession = databaseSessions . get ( databaseSessionId ) !
420+ const typesSubQuery = dbSession . drizzle
421+ . $with ( 'types' )
422+ . as (
423+ dbSession . drizzle . select ( {
424+ nspname : postgresPgCatalogPgNamespace . nspname ,
425+ objName : sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'format_type' ) } (${ postgresPgCatalogPgType . oid } , NULL)` . as ( 'obj_name' ) ,
426+ objType : sql < string > `
427+ CASE
428+ WHEN ${ postgresPgCatalogPgType . typrelid } != 0 THEN CAST('tuple' AS pg_catalog.text)
429+ WHEN ${ postgresPgCatalogPgType . typlen } < 0 THEN CAST('var' AS pg_catalog.text)
430+ ELSE CAST(${ postgresPgCatalogPgType . typlen } AS pg_catalog.text)
431+ END` . as ( 'obj_type' ) ,
432+ description : sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'obj_description' ) } (${ postgresPgCatalogPgType . oid } , 'pg_type')` . as ( 'description' ) ,
433+ } )
434+ . from ( postgresPgCatalogPgType )
435+ . leftJoin ( postgresPgCatalogPgNamespace , eq ( postgresPgCatalogPgType . typnamespace , postgresPgCatalogPgNamespace . oid ) )
436+ . where (
437+ and (
438+ or (
439+ eq ( postgresPgCatalogPgType . typrelid , 0 ) ,
440+ dbSession . drizzle
441+ . select ( { c : eq ( postgresPgCatalogPgClass . relkind , 'c' ) } )
442+ . from ( postgresPgCatalogPgClass )
443+ . where ( eq ( postgresPgCatalogPgClass . oid , postgresPgCatalogPgType . typrelid ) ) ,
444+ ) ,
445+ notExists (
446+ dbSession . drizzle
447+ . select ( {
448+ 1 : sql `1` ,
449+ } )
450+ . from ( postgresPgCatalogPgType )
451+ . where (
452+ and (
453+ eq ( postgresPgCatalogPgType . oid , postgresPgCatalogPgType . typelem ) ,
454+ eq ( postgresPgCatalogPgType . typarray , postgresPgCatalogPgType . oid ) ,
455+ ) ,
456+ ) ,
457+ ) ,
458+ ne ( postgresPgCatalogPgNamespace . nspname , 'pg_catalog' ) ,
459+ ne ( postgresPgCatalogPgNamespace . nspname , 'information_schema' ) ,
460+ notLike ( postgresPgCatalogPgNamespace . nspname , 'pg_toast%' ) ,
461+ ) ,
462+ ) ,
463+ )
464+
465+ const colsSubQuery = dbSession . drizzle
466+ . $with ( 'cols' )
467+ . as (
468+ dbSession . drizzle . select ( {
469+ schemaName : postgresPgCatalogPgNamespace . nspname ,
470+ objName : sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'format_type' ) } (${ postgresPgCatalogPgType . oid } , NULL)` . as ( 'obj_name' ) ,
471+ columnName : postgresPgCatalogPgAttribute . attname ,
472+ dataType : sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'format_type' ) } (${ postgresPgCatalogPgAttribute . atttypid } , ${ postgresPgCatalogPgAttribute . atttypmod } )` . as ( 'data_type' ) ,
473+ isRequired : postgresPgCatalogPgAttribute . attnotnull ,
474+ ordinalPosition : postgresPgCatalogPgAttribute . attnum ,
475+ description : sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'col_description' ) } (${ postgresPgCatalogPgAttribute . attrelid } , ${ postgresPgCatalogPgAttribute . attnum } )` . as ( 'description' ) ,
476+ } )
477+ . from ( postgresPgCatalogPgAttribute )
478+ . leftJoin ( postgresPgCatalogPgType , eq ( postgresPgCatalogPgAttribute . atttypid , postgresPgCatalogPgType . oid ) )
479+ . leftJoin ( postgresPgCatalogPgNamespace , eq ( postgresPgCatalogPgType . typnamespace , postgresPgCatalogPgNamespace . oid ) )
480+ . leftJoin ( sql `types` , and (
481+ eq ( typesSubQuery . nspname , postgresPgCatalogPgNamespace . nspname ) ,
482+ eq ( typesSubQuery . objName , sql < string > `${ sql . identifier ( 'pg_catalog' ) } .${ sql . identifier ( 'format_type' ) } (${ postgresPgCatalogPgType . oid } , NULL)` ) ,
483+ ) )
484+ . where (
485+ and (
486+ gt ( postgresPgCatalogPgAttribute . attnum , 0 ) ,
487+ eq ( postgresPgCatalogPgAttribute . attisdropped , false ) ,
488+ ) ,
489+ ) ,
490+ )
491+
492+ const results = await dbSession . drizzle
493+ . with ( typesSubQuery , colsSubQuery )
494+ . select ( {
495+ schemaName : colsSubQuery . schemaName ,
496+ objName : colsSubQuery . objName ,
497+ columnName : colsSubQuery . columnName ,
498+ dataType : colsSubQuery . dataType ,
499+ ordinalPosition : colsSubQuery . ordinalPosition ,
500+ isRequired : colsSubQuery . isRequired ,
501+ description : sql < string > `coalesce(${ colsSubQuery . description } , '')` . as ( 'description' ) ,
502+ } )
503+ . from ( colsSubQuery )
504+ . orderBy ( colsSubQuery . schemaName , colsSubQuery . objName , colsSubQuery . ordinalPosition )
505+
506+ return {
507+ databaseSessionId,
508+ results,
509+ }
510+ }
511+ catch ( err ) {
512+ log . withError ( err ) . withFields ( { databaseSessionId } ) . error ( 'failed to query local PGLite database to list user-defined types' )
265513 throw err
266514 }
267515 } )
0 commit comments