postgresai 0.17.0-dev.2 → 0.17.0-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  -- Role creation / password update (template-filled by cli/lib/init.ts)
2
2
  --
3
- -- Always uses a race-safe pattern (create if missing, then always alter to set the password):
3
+ -- A race-safe pattern: create if missing, then alter to set the password.
4
+ -- `pgai connect` omits the alter, so an existing role keeps its password:
4
5
  -- do $$ begin
5
6
  -- if not exists (select 1 from pg_catalog.pg_roles where rolname = '...') then
6
7
  -- begin
@@ -1,6 +1,4 @@
1
1
  -- Helper functions for postgres_ai monitoring user (template-filled by cli/lib/init.ts)
2
- -- These functions use SECURITY DEFINER to allow the monitoring user to perform
3
- -- operations they don't have direct permissions for.
4
2
 
5
3
  /*
6
4
  * table_describe
@@ -15,18 +13,25 @@
15
13
  *
16
14
  * Usage:
17
15
  * select postgres_ai.table_describe('public.users');
18
- * select postgres_ai.table_describe('my_table'); -- uses search_path
16
+ * select postgres_ai.table_describe('my_table'); -- pg_catalog, then public
17
+ *
18
+ * SECURITY INVOKER: it reads only the world-readable catalog, so it needs no
19
+ * privilege the caller lacks. Names are resolved by catalog query, not regclass
20
+ * (which demands USAGE on the schema): unqualified in pg_catalog, then public.
21
+ * search_path is pg_catalog, pg_temp so objects in a writable schema cannot
22
+ * shadow a built-in the function calls.
19
23
  */
20
24
  create or replace function postgres_ai.table_describe(
21
25
  in table_name text,
22
26
  out result text
23
27
  )
24
28
  language plpgsql
25
- security definer
26
- set search_path = pg_catalog, public
29
+ security invoker
30
+ set search_path = pg_catalog, pg_temp
27
31
  as $$
28
32
  declare
29
33
  v_oid oid;
34
+ v_ident text[];
30
35
  v_schema text;
31
36
  v_table text;
32
37
  v_relkind char;
@@ -37,8 +42,24 @@ declare
37
42
  v_rec record;
38
43
  v_constraint_count int := 0;
39
44
  begin
40
- -- Resolve table name to OID (handles schema-qualified and search_path)
41
- v_oid := table_name::regclass::oid;
45
+ -- Resolve table name to OID: [current database.]schema.table (pg_temp is the
46
+ -- caller's temp schema), or an unqualified name in pg_catalog, then public
47
+ v_ident := parse_ident(table_name);
48
+ select c.oid into v_oid
49
+ from pg_class c
50
+ join pg_namespace n on n.oid = c.relnamespace
51
+ where (cardinality(v_ident) <= 2 or (cardinality(v_ident) = 3 and v_ident[1] = current_database()))
52
+ and c.relname = v_ident[cardinality(v_ident)]::name
53
+ and case
54
+ when cardinality(v_ident) = 1 then n.nspname in ('pg_catalog', 'public')
55
+ when v_ident[cardinality(v_ident) - 1] = 'pg_temp' then n.oid = pg_my_temp_schema()
56
+ else n.nspname = v_ident[cardinality(v_ident) - 1]::name
57
+ end
58
+ order by n.nspname = 'public'
59
+ limit 1;
60
+ if v_oid is null then
61
+ raise exception 'relation "%" does not exist', table_name using errcode = '42P01';
62
+ end if;
42
63
 
43
64
  -- Get basic table info
44
65
  select
@@ -207,7 +228,7 @@ begin
207
228
  when 'f' then v_line := v_line || 'FK: ';
208
229
  when 'u' then v_line := v_line || 'UNIQUE: ';
209
230
  when 'c' then v_line := v_line || 'CHECK: ';
210
- else v_line := v_line || v_rec.contype || ': ';
231
+ else v_line := v_line || v_rec.contype::text || ': ';
211
232
  end case;
212
233
  v_line := v_line || v_rec.conname || ' ' || v_rec.condef;
213
234
  v_lines := array_append(v_lines, v_line);
@@ -1,6 +1,7 @@
1
1
  -- Role creation / password update (template-filled by cli/lib/init.ts)
2
2
  --
3
- -- Always uses a race-safe pattern (create if missing, then always alter to set the password):
3
+ -- A race-safe pattern: create if missing, then alter to set the password.
4
+ -- `pgai connect` omits the alter, so an existing role keeps its password:
4
5
  -- do $$ begin
5
6
  -- if not exists (select 1 from pg_catalog.pg_roles where rolname = '...') then
6
7
  -- begin
@@ -1,6 +1,4 @@
1
1
  -- Helper functions for postgres_ai monitoring user (template-filled by cli/lib/init.ts)
2
- -- These functions use SECURITY DEFINER to allow the monitoring user to perform
3
- -- operations they don't have direct permissions for.
4
2
 
5
3
  /*
6
4
  * table_describe
@@ -15,18 +13,25 @@
15
13
  *
16
14
  * Usage:
17
15
  * select postgres_ai.table_describe('public.users');
18
- * select postgres_ai.table_describe('my_table'); -- uses search_path
16
+ * select postgres_ai.table_describe('my_table'); -- pg_catalog, then public
17
+ *
18
+ * SECURITY INVOKER: it reads only the world-readable catalog, so it needs no
19
+ * privilege the caller lacks. Names are resolved by catalog query, not regclass
20
+ * (which demands USAGE on the schema): unqualified in pg_catalog, then public.
21
+ * search_path is pg_catalog, pg_temp so objects in a writable schema cannot
22
+ * shadow a built-in the function calls.
19
23
  */
20
24
  create or replace function postgres_ai.table_describe(
21
25
  in table_name text,
22
26
  out result text
23
27
  )
24
28
  language plpgsql
25
- security definer
26
- set search_path = pg_catalog, public
29
+ security invoker
30
+ set search_path = pg_catalog, pg_temp
27
31
  as $$
28
32
  declare
29
33
  v_oid oid;
34
+ v_ident text[];
30
35
  v_schema text;
31
36
  v_table text;
32
37
  v_relkind char;
@@ -37,8 +42,24 @@ declare
37
42
  v_rec record;
38
43
  v_constraint_count int := 0;
39
44
  begin
40
- -- Resolve table name to OID (handles schema-qualified and search_path)
41
- v_oid := table_name::regclass::oid;
45
+ -- Resolve table name to OID: [current database.]schema.table (pg_temp is the
46
+ -- caller's temp schema), or an unqualified name in pg_catalog, then public
47
+ v_ident := parse_ident(table_name);
48
+ select c.oid into v_oid
49
+ from pg_class c
50
+ join pg_namespace n on n.oid = c.relnamespace
51
+ where (cardinality(v_ident) <= 2 or (cardinality(v_ident) = 3 and v_ident[1] = current_database()))
52
+ and c.relname = v_ident[cardinality(v_ident)]::name
53
+ and case
54
+ when cardinality(v_ident) = 1 then n.nspname in ('pg_catalog', 'public')
55
+ when v_ident[cardinality(v_ident) - 1] = 'pg_temp' then n.oid = pg_my_temp_schema()
56
+ else n.nspname = v_ident[cardinality(v_ident) - 1]::name
57
+ end
58
+ order by n.nspname = 'public'
59
+ limit 1;
60
+ if v_oid is null then
61
+ raise exception 'relation "%" does not exist', table_name using errcode = '42P01';
62
+ end if;
42
63
 
43
64
  -- Get basic table info
44
65
  select
@@ -207,7 +228,7 @@ begin
207
228
  when 'f' then v_line := v_line || 'FK: ';
208
229
  when 'u' then v_line := v_line || 'UNIQUE: ';
209
230
  when 'c' then v_line := v_line || 'CHECK: ';
210
- else v_line := v_line || v_rec.contype || ': ';
231
+ else v_line := v_line || v_rec.contype::text || ': ';
211
232
  end case;
212
233
  v_line := v_line || v_rec.conname || ' ' || v_rec.condef;
213
234
  v_lines := array_append(v_lines, v_line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.17.0-dev.2",
3
+ "version": "0.17.0-dev.4",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
package/sql/01.role.sql CHANGED
@@ -1,6 +1,7 @@
1
1
  -- Role creation / password update (template-filled by cli/lib/init.ts)
2
2
  --
3
- -- Always uses a race-safe pattern (create if missing, then always alter to set the password):
3
+ -- A race-safe pattern: create if missing, then alter to set the password.
4
+ -- `pgai connect` omits the alter, so an existing role keeps its password:
4
5
  -- do $$ begin
5
6
  -- if not exists (select 1 from pg_catalog.pg_roles where rolname = '...') then
6
7
  -- begin
@@ -1,6 +1,4 @@
1
1
  -- Helper functions for postgres_ai monitoring user (template-filled by cli/lib/init.ts)
2
- -- These functions use SECURITY DEFINER to allow the monitoring user to perform
3
- -- operations they don't have direct permissions for.
4
2
 
5
3
  /*
6
4
  * table_describe
@@ -15,18 +13,25 @@
15
13
  *
16
14
  * Usage:
17
15
  * select postgres_ai.table_describe('public.users');
18
- * select postgres_ai.table_describe('my_table'); -- uses search_path
16
+ * select postgres_ai.table_describe('my_table'); -- pg_catalog, then public
17
+ *
18
+ * SECURITY INVOKER: it reads only the world-readable catalog, so it needs no
19
+ * privilege the caller lacks. Names are resolved by catalog query, not regclass
20
+ * (which demands USAGE on the schema): unqualified in pg_catalog, then public.
21
+ * search_path is pg_catalog, pg_temp so objects in a writable schema cannot
22
+ * shadow a built-in the function calls.
19
23
  */
20
24
  create or replace function postgres_ai.table_describe(
21
25
  in table_name text,
22
26
  out result text
23
27
  )
24
28
  language plpgsql
25
- security definer
26
- set search_path = pg_catalog, public
29
+ security invoker
30
+ set search_path = pg_catalog, pg_temp
27
31
  as $$
28
32
  declare
29
33
  v_oid oid;
34
+ v_ident text[];
30
35
  v_schema text;
31
36
  v_table text;
32
37
  v_relkind char;
@@ -37,8 +42,24 @@ declare
37
42
  v_rec record;
38
43
  v_constraint_count int := 0;
39
44
  begin
40
- -- Resolve table name to OID (handles schema-qualified and search_path)
41
- v_oid := table_name::regclass::oid;
45
+ -- Resolve table name to OID: [current database.]schema.table (pg_temp is the
46
+ -- caller's temp schema), or an unqualified name in pg_catalog, then public
47
+ v_ident := parse_ident(table_name);
48
+ select c.oid into v_oid
49
+ from pg_class c
50
+ join pg_namespace n on n.oid = c.relnamespace
51
+ where (cardinality(v_ident) <= 2 or (cardinality(v_ident) = 3 and v_ident[1] = current_database()))
52
+ and c.relname = v_ident[cardinality(v_ident)]::name
53
+ and case
54
+ when cardinality(v_ident) = 1 then n.nspname in ('pg_catalog', 'public')
55
+ when v_ident[cardinality(v_ident) - 1] = 'pg_temp' then n.oid = pg_my_temp_schema()
56
+ else n.nspname = v_ident[cardinality(v_ident) - 1]::name
57
+ end
58
+ order by n.nspname = 'public'
59
+ limit 1;
60
+ if v_oid is null then
61
+ raise exception 'relation "%" does not exist', table_name using errcode = '42P01';
62
+ end if;
42
63
 
43
64
  -- Get basic table info
44
65
  select
@@ -207,7 +228,7 @@ begin
207
228
  when 'f' then v_line := v_line || 'FK: ';
208
229
  when 'u' then v_line := v_line || 'UNIQUE: ';
209
230
  when 'c' then v_line := v_line || 'CHECK: ';
210
- else v_line := v_line || v_rec.contype || ': ';
231
+ else v_line := v_line || v_rec.contype::text || ': ';
211
232
  end case;
212
233
  v_line := v_line || v_rec.conname || ' ' || v_rec.condef;
213
234
  v_lines := array_append(v_lines, v_line);